diff --git a/.dockerignore b/.dockerignore
index 2e6259d23d..a4b8a25cdc 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -1,6 +1,5 @@
.dockerignore
.editorconfig
-.travis.yml
GPLv3.txt
LICENSE
README.md
@@ -23,8 +22,6 @@ tgstation.int
tgstation.rsc
tgstation.lk
tgstation.dyn.rsc
-libmariadb.dll
-rust_g.dll
-BSQL.dll
-appveyor.yml
+*.dll
Dockerfile
+tools/bootstrap/.cache
diff --git a/.editorconfig b/.editorconfig
index c44572fbf9..d6adf33378 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -4,7 +4,7 @@ indent_size = 4
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
-#end_of_line = lf
+# end_of_line = lf
[*.yml]
indent_style = space
@@ -12,3 +12,9 @@ indent_size = 2
[*.py]
indent_style = space
+
+[*.md]
+trim_trailing_whitespace = false
+
+[Dockerfile]
+indent_style = space
diff --git a/.gitattributes b/.gitattributes
index c447869d3e..f46a38d1d1 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,8 +1,7 @@
* text=auto
## Enforce text mode and LF line breaks
-## porter note: not yet LFing dm.
-# *.bat text eol=lf
+*.bat text eol=lf
*.css text eol=lf
# *.dm text eol=lf
# *.dme text eol=lf
@@ -12,15 +11,15 @@
*.js text eol=lf
*.json text eol=lf
*.jsx text eol=lf
-# *.md text eol=lf
+*.md text eol=lf
*.py text eol=lf
*.scss text eol=lf
-# *.sh text eol=lf
-# *.sql 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
+*.txt text eol=lf
*.yaml text eol=lf
*.yml text eol=lf
@@ -38,5 +37,8 @@
*.dmm text eol=lf merge=dmm
*.dmi binary merge=dmi
+##Force tab indents on dm files
+*.dm whitespace=indent-with-non-tab
+
## Force changelog merging to use union
html/changelog.html text eol=lf merge=union
diff --git a/.github/workflows/ci_suite.yml b/.github/workflows/ci_suite.yml
new file mode 100644
index 0000000000..a6b91bd559
--- /dev/null
+++ b/.github/workflows/ci_suite.yml
@@ -0,0 +1,120 @@
+name: CI Suite
+on:
+ push:
+ branches:
+ - master
+ pull_request:
+ branches:
+ - master
+jobs:
+ run_linters:
+ if: "!contains(github.event.head_commit.message, '[ci skip]')"
+ name: Run Linters
+ runs-on: ubuntu-20.04
+ steps:
+ - uses: actions/checkout@v2
+ - name: Setup cache
+ uses: actions/cache@v2
+ with:
+ path: $HOME/SpacemanDMM
+ key: ${{ runner.os }}-spacemandmm
+ - name: Install Tools
+ run: |
+ pip3 install setuptools
+ bash tools/ci/install_node.sh
+ bash tools/ci/install_spaceman_dmm.sh dreamchecker
+ tools/bootstrap/python -c ''
+ - name: Run Linters
+ run: |
+ bash tools/ci/check_filedirs.sh tgstation.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 > ${GITHUB_WORKSPACE}/output-annotations.txt 2>&1
+ - name: Annotate Lints
+ uses: yogstation13/DreamAnnotate@v1
+ if: always()
+ with:
+ outputFile: output-annotations.txt
+
+ compile_all_maps:
+ if: "!contains(github.event.head_commit.message, '[ci skip]')"
+ name: Compile Maps
+ runs-on: ubuntu-20.04
+ steps:
+ - uses: actions/checkout@v2
+ - name: Setup cache
+ uses: actions/cache@v2
+ with:
+ path: $HOME/BYOND
+ key: ${{ runner.os }}-byond
+ - name: Compile All Maps
+ run: |
+ bash tools/ci/install_byond.sh
+ source $HOME/BYOND/byond/bin/byondsetup
+ python3 tools/ci/template_dm_generator.py
+ tgui/bin/tgui --build
+ bash tools/ci/dm.sh -DCIBUILDING -DCITESTING -DALL_MAPS tgstation.dme
+
+ run_all_tests:
+ if: "!contains(github.event.head_commit.message, '[ci skip]')"
+ name: Integration Tests
+ runs-on: ubuntu-20.04
+ 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
+ uses: actions/cache@v2
+ with:
+ path: $HOME/BYOND
+ key: ${{ runner.os }}-byond
+ - name: Setup database
+ run: |
+ sudo systemctl start mysql
+ mysql -u root -proot -e 'CREATE DATABASE tg_ci;'
+ mysql -u root -proot tg_ci < SQL/tgstation_schema.sql
+ mysql -u root -proot -e 'CREATE DATABASE tg_ci_prefixed;'
+ mysql -u root -proot tg_ci_prefixed < SQL/tgstation_schema_prefixed.sql
+ - name: Install rust-g
+ run: |
+ sudo dpkg --add-architecture i386
+ sudo apt update || true
+ sudo apt install -o APT::Immediate-Configure=false libssl1.1:i386
+ bash tools/ci/install_rust_g.sh
+ - name: Compile and run tests
+ run: |
+ bash tools/ci/install_byond.sh
+ source $HOME/BYOND/byond/bin/byondsetup
+ tgui/bin/tgui --build
+ bash tools/ci/dm.sh -DCIBUILDING tgstation.dme
+ # bash tools/ci/run_server.sh
+
+ test_windows:
+ if: "!contains(github.event.head_commit.message, '[ci skip]')"
+ name: Windows Build
+ runs-on: windows-latest
+ steps:
+ - uses: actions/checkout@v2
+ - name: Compile
+ run: pwsh tools/ci/build.ps1
+ - name: Create artifact
+ run: |
+ md deploy
+ bash tools/deploy.sh ./deploy
+ - name: Deploy artifact
+ uses: actions/upload-artifact@v2
+ with:
+ name: deploy
+ path: deploy
diff --git a/.github/workflows/compile_changelogs.yml b/.github/workflows/compile_changelogs.yml
index 4fd396f133..b0e1578815 100644
--- a/.github/workflows/compile_changelogs.yml
+++ b/.github/workflows/compile_changelogs.yml
@@ -7,7 +7,7 @@ on:
jobs:
compile:
name: "Compile changelogs"
- runs-on: ubuntu-latest
+ runs-on: ubuntu-20.04
steps:
- name: "Check for CHANGELOG_ENABLER secret and pass true to output if it exists to be checked by later steps"
id: value_holder
@@ -15,7 +15,7 @@ jobs:
CHANGELOG_ENABLER: ${{ secrets.CHANGELOG_ENABLER }}
run: |
unset SECRET_EXISTS
- if [-n $CHANGELOG_ENABLER]; then SECRET_EXISTS='true' ; fi
+ if [ -n $CHANGELOG_ENABLER ]; then SECRET_EXISTS='true' ; fi
echo ::set-output name=CL_ENABLED::${SECRET_EXISTS}
- name: "Setup python"
if: steps.value_holder.outputs.CL_ENABLED
diff --git a/.github/workflows/docker_publish.yml b/.github/workflows/docker_publish.yml
new file mode 100644
index 0000000000..32d160b840
--- /dev/null
+++ b/.github/workflows/docker_publish.yml
@@ -0,0 +1,23 @@
+name: Docker Build
+
+on:
+ push:
+ branches:
+ - master
+
+jobs:
+ publish:
+ if: "!contains(github.event.head_commit.message, '[ci skip]')"
+ runs-on: ubuntu-20.04
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Build and Publish Docker Image to Registry
+ uses: elgohr/Publish-Docker-Github-Action@master
+ with:
+ name: tgstation/tgstation
+ username: ${{ secrets.DOCKER_USERNAME }}
+ password: ${{ secrets.DOCKER_PASSWORD }}
+ dockerfile: Dockerfile
+ tags: "latest"
+ cache: true
diff --git a/.github/workflows/generate_documentation.yml b/.github/workflows/generate_documentation.yml
new file mode 100644
index 0000000000..c1b7a2d5e1
--- /dev/null
+++ b/.github/workflows/generate_documentation.yml
@@ -0,0 +1,31 @@
+name: Generate documentation
+on:
+ push:
+ branches:
+ - master
+jobs:
+ generate_documentation:
+ if: "!contains(github.event.head_commit.message, '[ci skip]')"
+ runs-on: ubuntu-20.04
+ steps:
+ - uses: actions/checkout@v2
+ - name: Setup cache
+ uses: actions/cache@v2
+ with:
+ path: $HOME/SpacemanDMM
+ key: ${{ runner.os }}-spacemandmm
+ - name: Install SpacemanDMM
+ run: bash tools/ci/install_spaceman_dmm.sh dmdoc
+ - name: Generate documentation
+ run: |
+ ~/dmdoc
+ touch dmdoc/.nojekyll
+ echo codedocs.tgstation13.org > dmdoc/CNAME
+ - name: Deploy
+ uses: JamesIves/github-pages-deploy-action@3.7.1
+ with:
+ BRANCH: gh-pages
+ CLEAN: true
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ SINGLE_COMMIT: true
+ FOLDER: dmdoc
diff --git a/.github/workflows/round_id_linker.yml b/.github/workflows/round_id_linker.yml
index 37998a93eb..2b028b0863 100644
--- a/.github/workflows/round_id_linker.yml
+++ b/.github/workflows/round_id_linker.yml
@@ -5,7 +5,7 @@ on:
jobs:
link_rounds:
- runs-on: ubuntu-latest
+ runs-on: ubuntu-20.04
steps:
- uses: Cidatel-Station-13/round_linker@master #notice: fork the round linkies from tg!!
with:
diff --git a/.github/workflows/update_tgs_dmapi.yml b/.github/workflows/update_tgs_dmapi.yml
index 6fe53f700c..19a72d6702 100644
--- a/.github/workflows/update_tgs_dmapi.yml
+++ b/.github/workflows/update_tgs_dmapi.yml
@@ -7,7 +7,7 @@ on:
jobs:
update-dmapi:
- runs-on: ubuntu-latest
+ runs-on: ubuntu-20.04
name: Update the TGS DMAPI
steps:
- name: Clone
@@ -26,6 +26,7 @@ jobs:
library-path: 'code/modules/tgs'
- name: Commit and Push
+ continue-on-error: true
run: |
git config user.name tgstation-server
git config user.email tgstation-server@users.noreply.github.com
@@ -35,6 +36,7 @@ jobs:
- name: Create Pull Request
uses: repo-sync/pull-request@v2
+ if: ${{ success() }}
with:
source_branch: "tgs-dmapi-update"
destination_branch: "master"
@@ -42,4 +44,4 @@ jobs:
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_allow_empty: false
- github_token: ${{ secrets.GITHUB_TOKEN }}
+ github_token: ${{ secrets.TGS_UPDATER }}
diff --git a/.gitignore b/.gitignore
index e885761104..dd20fecd22 100644
--- a/.gitignore
+++ b/.gitignore
@@ -231,3 +231,6 @@ tools/LinuxOneShot/Database
tools/LinuxOneShot/TGS_Config
tools/LinuxOneShot/TGS_Instances
tools/LinuxOneShot/TGS_Logs
+
+# Common build tooling
+!/tools/build
diff --git a/.tgs4.yml b/.tgs4.yml
new file mode 100644
index 0000000000..932a3a6672
--- /dev/null
+++ b/.tgs4.yml
@@ -0,0 +1,8 @@
+static_files:
+ - name: config
+ populate: true
+ - name: data
+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 83ef6fa8ff..0000000000
--- a/.travis.yml
+++ /dev/null
@@ -1,102 +0,0 @@
-language: generic
-os: linux
-dist: xenial
-
-branches:
- except:
- - ___TGS3TempBranch
- - ___TGSTempBranch
-
-jobs:
- include:
- - name: "Run Linters"
- addons:
- apt:
- packages:
- - python3
- - python3-pip
- - python3-setuptools
- - pcregrep
- - rustc
- - cargo
- cache:
- directories:
- - $HOME/SpacemanDMM
- install:
- - tools/travis/install_build_tools.sh
- - tools/travis/install_spaceman_dmm.sh dreamchecker
- script:
- - tools/travis/check_filedirs.sh tgstation.dme
- - tools/travis/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
- - tools/travis/build_tgui.sh
- - tools/travis/check_grep.sh
- - python3 tools/travis/check_line_endings.py
- - ~/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 tgstation.dme
-
- - name: "Compile and Run Tests"
- addons:
- mariadb: '10.2'
- apt:
- sources:
- - ubuntu-toolchain-r-test
- packages:
- - libstdc++6:i386
- - gcc-multilib
- - g++-7
- - g++-7-multilib
- - libmariadb-client-lgpl-dev:i386
- - libmariadbd-dev
- cache:
- directories:
- - $HOME/BYOND
- - $HOME/libmariadb
- install:
- - tools/travis/install_byond.sh
- - source $HOME/BYOND/byond/bin/byondsetup
- - tools/travis/install_libmariadb.sh
- - tools/travis/install_rust_g.sh
- before_script:
- - mysql -u root -e 'CREATE DATABASE tg_travis;'
- - mysql -u root tg_travis < SQL/tgstation_schema.sql
- - mysql -u root -e 'CREATE DATABASE tg_travis_prefixed;'
- - mysql -u root tg_travis_prefixed < SQL/tgstation_schema_prefixed.sql
- - tools/travis/build_bsql.sh
- script:
- - tools/travis/dm.sh -DTRAVISBUILDING tgstation.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 $TRAVIS_BRANCH || true
- # script:
- # - ~/dmdoc
- # - touch dmdoc/.nojekyll
- # deploy:
- # provider: pages
- # skip_cleanup: true
- # local_dir: dmdoc
- # token: $DMDOC_GITHUB_TOKEN
- # fqdn: codedocs.tgstation13.org
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
index f79100f563..d384c4535b 100644
--- a/.vscode/extensions.json
+++ b/.vscode/extensions.json
@@ -1,9 +1,11 @@
{
- "recommendations": [
- "gbasood.byond-dm-language-support",
- "platymuus.dm-langclient",
+ "recommendations": [
+ "gbasood.byond-dm-language-support",
+ "platymuus.dm-langclient",
"EditorConfig.EditorConfig",
"arcanis.vscode-zipfs",
- "dbaeumer.vscode-eslint"
- ]
+ "dbaeumer.vscode-eslint",
+ "stylemistake.auto-comment-blocks",
+ "Donkie.vscode-tgstation-test-adapter"
+ ]
}
diff --git a/.vscode/settings.json b/.vscode/settings.json
index f95b8f7cc6..f290e9f369 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,8 +1,10 @@
{
- "eslint.nodePath": "tgui/.yarn/sdks",
+ "eslint.nodePath": "./tgui/.yarn/sdks",
"eslint.workingDirectories": [
"./tgui"
],
+ "typescript.tsdk": "./tgui/.yarn/sdks/typescript/lib",
+ "typescript.enablePromptUseWorkspaceTsdk": true,
"search.exclude": {
"tgui/.yarn": true,
"tgui/.pnp.*": true
@@ -14,5 +16,6 @@
}
],
"files.eol": "\n",
- "gitlens.advanced.blame.customArguments": ["-w"]
+ "gitlens.advanced.blame.customArguments": ["-w"],
+ "tgstationTestExplorer.project.resultsType": "json"
}
diff --git a/BSQL.dll b/BSQL.dll
deleted file mode 100644
index 861492c8b4..0000000000
Binary files a/BSQL.dll and /dev/null differ
diff --git a/Build.bat b/Build.bat
new file mode 100644
index 0000000000..dd3a6fd9d9
--- /dev/null
+++ b/Build.bat
@@ -0,0 +1,2 @@
+@call tools\build\build
+@pause
diff --git a/Dockerfile b/Dockerfile
index e8a5f44908..0ae4f82be8 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,90 +1,85 @@
-FROM tgstation/byond:513.1508 as base
+# base = ubuntu + full apt update
+FROM ubuntu:xenial AS base
-FROM base as build_base
-
-RUN apt-get update \
+RUN dpkg --add-architecture i386 \
+ && apt-get update \
+ && apt-get upgrade -y \
+ && apt-get dist-upgrade -y \
&& apt-get install -y --no-install-recommends \
- git \
- ca-certificates
+ ca-certificates
-FROM build_base as rust_g
+# byond = base + byond installed globally
+FROM base AS byond
+WORKDIR /byond
+RUN apt-get install -y --no-install-recommends \
+ curl \
+ unzip \
+ make \
+ libstdc++6:i386
+
+COPY dependencies.sh .
+
+RUN . ./dependencies.sh \
+ && curl "http://www.byond.com/download/build/${BYOND_MAJOR}/${BYOND_MAJOR}.${BYOND_MINOR}_byond_linux.zip" -o byond.zip \
+ && unzip byond.zip \
+ && cd byond \
+ && sed -i 's|install:|&\n\tmkdir -p $(MAN_DIR)/man6|' Makefile \
+ && make install \
+ && chmod 644 /usr/local/byond/man/man6/* \
+ && apt-get purge -y --auto-remove curl unzip make \
+ && cd .. \
+ && rm -rf byond byond.zip
+
+# build = byond + tgstation compiled and deployed to /deploy
+FROM byond AS build
+WORKDIR /tgstation
+
+RUN apt-get install -y --no-install-recommends \
+ curl
+
+COPY . .
+
+RUN env TG_BOOTSTRAP_NODE_LINUX=1 tools/build/build \
+ && tools/deploy.sh /deploy
+
+# rust = base + rustc and i686 target
+FROM base AS rust
+RUN apt-get install -y --no-install-recommends \
+ curl && \
+ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal \
+ && ~/.cargo/bin/rustup target add i686-unknown-linux-gnu
+
+# rust_g = base + rust_g compiled to /rust_g
+FROM rust 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 \
+ pkg-config:i386 \
+ libssl-dev:i386 \
+ gcc-multilib \
+ git \
&& git init \
&& git remote add origin https://github.com/tgstation/rust-g
COPY dependencies.sh .
-RUN /bin/bash -c "source dependencies.sh \
- && git fetch --depth 1 origin \$RUST_G_VERSION" \
+RUN . ./dependencies.sh \
+ && git fetch --depth 1 origin "${RUST_G_VERSION}" \
&& git checkout FETCH_HEAD \
- && ~/.cargo/bin/cargo build --release
-
-FROM build_base as bsql
-
-WORKDIR /bsql
-
-RUN apt-get install -y --no-install-recommends software-properties-common \
- && add-apt-repository ppa:ubuntu-toolchain-r/test \
- && apt-get update \
- && apt-get install -y --no-install-recommends \
- cmake \
- make \
- g++-7 \
- libmariadb-client-lgpl-dev \
- && git init \
- && git remote add origin https://github.com/tgstation/BSQL
-
-COPY dependencies.sh .
-
-RUN /bin/bash -c "source dependencies.sh \
- && git fetch --depth 1 origin \$BSQL_VERSION" \
- && git checkout FETCH_HEAD
-
-WORKDIR /bsql/artifacts
-
-ENV CC=gcc-7 CXX=g++-7
-
-RUN ln -s /usr/include/mariadb /usr/include/mysql \
- && ln -s /usr/lib/i386-linux-gnu /root/MariaDB \
- && cmake .. \
- && make
-
-FROM base as dm_base
+ && env PKG_CONFIG_ALLOW_CROSS=1 ~/.cargo/bin/cargo build --release --target i686-unknown-linux-gnu
+# final = byond + runtime deps + rust_g + build
+FROM byond
WORKDIR /tgstation
-FROM dm_base as build
+RUN apt-get install -y --no-install-recommends \
+ libssl1.0.0:i386 \
+ zlib1g:i386
-COPY . .
-
-RUN DreamMaker -max_errors 0 tgstation.dme && tools/deploy.sh /deploy
-
-FROM dm_base
-
-EXPOSE 1337
-
-RUN apt-get update \
- && 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=bsql /bsql/artifacts/src/BSQL/libBSQL.so ./
COPY --from=build /deploy ./
-
-#bsql fexists memes
-RUN ln -s /tgstation/libBSQL.so /root/.byond/bin/libBSQL.so
+COPY --from=rust_g /rust_g/target/i686-unknown-linux-gnu/release/librust_g.so ./librust_g.so
VOLUME [ "/tgstation/config", "/tgstation/data" ]
-
ENTRYPOINT [ "DreamDaemon", "tgstation.dmb", "-port", "1337", "-trusted", "-close", "-verbose" ]
+EXPOSE 1337
diff --git a/SQL/ban_conversion_2018-10-28.py b/SQL/ban_conversion_2018-10-28.py
new file mode 100644
index 0000000000..26d928bfd1
--- /dev/null
+++ b/SQL/ban_conversion_2018-10-28.py
@@ -0,0 +1,174 @@
+#Python 3+ Script for converting ban table format as of 2018-10-28 made by Jordie0608
+#
+#Before starting ensure you have installed the mysqlclient package https://github.com/PyMySQL/mysqlclient-python
+#It can be downloaded from command line with pip:
+#pip install mysqlclient
+#
+#You will also have to create a new ban table for inserting converted data to per the schema:
+#CREATE TABLE `ban` (
+# `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
+# `bantime` DATETIME NOT NULL,
+# `server_ip` INT(10) UNSIGNED NOT NULL,
+# `server_port` SMALLINT(5) UNSIGNED NOT NULL,
+# `round_id` INT(11) UNSIGNED NOT NULL,
+# `role` VARCHAR(32) NULL DEFAULT NULL,
+# `expiration_time` DATETIME NULL DEFAULT NULL,
+# `applies_to_admins` TINYINT(1) UNSIGNED NOT NULL DEFAULT '0',
+# `reason` VARCHAR(2048) NOT NULL,
+# `ckey` VARCHAR(32) NULL DEFAULT NULL,
+# `ip` INT(10) UNSIGNED NULL DEFAULT NULL,
+# `computerid` VARCHAR(32) NULL DEFAULT NULL,
+# `a_ckey` VARCHAR(32) NOT NULL,
+# `a_ip` INT(10) UNSIGNED NOT NULL,
+# `a_computerid` VARCHAR(32) NOT NULL,
+# `who` VARCHAR(2048) NOT NULL,
+# `adminwho` VARCHAR(2048) NOT NULL,
+# `edits` TEXT NULL DEFAULT NULL,
+# `unbanned_datetime` DATETIME NULL DEFAULT NULL,
+# `unbanned_ckey` VARCHAR(32) NULL DEFAULT NULL,
+# `unbanned_ip` INT(10) UNSIGNED NULL DEFAULT NULL,
+# `unbanned_computerid` VARCHAR(32) NULL DEFAULT NULL,
+# `unbanned_round_id` INT(11) UNSIGNED NULL DEFAULT NULL,
+# PRIMARY KEY (`id`),
+# KEY `idx_ban_isbanned` (`ckey`,`role`,`unbanned_datetime`,`expiration_time`),
+# KEY `idx_ban_isbanned_details` (`ckey`,`ip`,`computerid`,`role`,`unbanned_datetime`,`expiration_time`),
+# KEY `idx_ban_count` (`bantime`,`a_ckey`,`applies_to_admins`,`unbanned_datetime`,`expiration_time`)
+#) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+#This is to prevent the destruction of existing data and allow rollbacks to be performed in the event of an error during conversion
+#Once conversion is complete remember to rename the old and new ban tables; it's up to you if you want to keep the old table
+#
+#To view the parameters for this script, execute it with the argument --help
+#All the positional arguments are required, remember to include prefixes in your table names if you use them
+#An example of the command used to execute this script from powershell:
+#python ban_conversion_2018-10-28.py "localhost" "root" "password" "feedback" "SS13_ban" "SS13_ban_new"
+#I found that this script would complete conversion of 35000 rows in approximately 20 seconds, results will depend on the size of your ban table and computer used
+#
+#The script has been tested to complete with tgstation's ban table as of 2018-09-02 02:19:56
+#In the event of an error the new ban table is automatically truncated
+#The source table is never modified so you don't have to worry about losing any data due to errors
+#Some additional error correction is performed to fix problems specific to legacy and invalid data in tgstation's ban table, these operations are tagged with a 'TG:' comment
+#Even if you don't have any of these specific problems in your ban table the operations won't have matter as they have an insignificant effect on runtime
+#
+#While this script is safe to run with your game server(s) active, any bans created after the script has started won't be converted
+#You will also have to ensure that the code and table names are updated between rounds as neither will be compatible
+
+import MySQLdb
+import argparse
+import sys
+from datetime import datetime
+
+def parse_role(bantype, job):
+ if bantype in ("PERMABAN", "TEMPBAN", "ADMIN_PERMABAN", "ADMIN_TEMPBAN"):
+ role = "Server"
+ else:
+ #TG: Some legacy jobbans are missing the last character from their job string.
+ job_name_fixes = {"A":"AI", "Captai":"Captain", "Cargo Technicia":"Cargo Technician", "Chaplai":"Chaplain", "Che":"Chef", "Chemis":"Chemist", "Chief Enginee":"Chief Engineer", "Chief Medical Office":"Chief Medical Officer", "Cybor":"Cyborg", "Detectiv":"Detective", "Head of Personne":"Head of Personnel", "Head of Securit":"Head of Security", "Mim":"Mime", "pA":"pAI", "Quartermaste":"Quartermaster", "Research Directo":"Research Director", "Scientis":"Scientist", "Security Office":"Security Officer", "Station Enginee":"Station Engineer", "Syndicat":"Syndicate", "Warde":"Warden"}
+ keep_job_names = ("AI", "Head of Personnel", "Head of Security", "OOC", "pAI")
+ if job in job_name_fixes:
+ role = job_name_fixes[job]
+ #Some job names we want to keep the same as .title() would return a different string.
+ elif job in keep_job_names:
+ role = job
+ #And then there's this asshole.
+ elif job == "servant of Ratvar":
+ role = "Servant of Ratvar"
+ else:
+ role = job.title()
+ return role
+
+def parse_admin(bantype):
+ if bantype in ("ADMIN_PERMABAN", "ADMIN_TEMPBAN"):
+ return 1
+ else:
+ return 0
+
+def parse_datetime(bantype, expiration_time):
+ if bantype in ("PERMABAN", "JOB_PERMABAN", "ADMIN_PERMABAN"):
+ expiration_time = None
+ #TG: two bans with an invalid expiration_time due to admins setting the duration to approx. 19 billion years, I'm going to count them as permabans.
+ elif expiration_time == "0000-00-00 00:00:00":
+ expiration_time = None
+ elif not expiration_time:
+ expiration_time = None
+ return expiration_time
+
+def parse_not_null(field):
+ if not field:
+ field = 0
+ return field
+
+def parse_for_empty(field):
+ if not field:
+ field = None
+ #TG: Several bans from 2012, probably from clients disconnecting while a ban was being made.
+ elif field == "BLANK CKEY ERROR":
+ field = None
+ return field
+
+if sys.version_info[0] < 3:
+ raise Exception("Python must be at least version 3 for this script.")
+current_round = 0
+parser = argparse.ArgumentParser()
+parser.add_argument("address", help="MySQL server address (use localhost for the current computer)")
+parser.add_argument("username", help="MySQL login username")
+parser.add_argument("password", help="MySQL login username")
+parser.add_argument("database", help="Database name")
+parser.add_argument("curtable", help="Name of the current ban table (remember prefixes if you use them)")
+parser.add_argument("newtable", help="Name of the new table to insert to, can't be same as the source table (remember prefixes)")
+args = parser.parse_args()
+db=MySQLdb.connect(host=args.address, user=args.username, passwd=args.password, db=args.database)
+cursor=db.cursor()
+current_table = args.curtable
+new_table = args.newtable
+#TG: Due to deleted rows and a legacy ban import being inserted from id 3140 id order is not contiguous or in line with date order. While technically valid, it's confusing and I don't like that.
+#TG: So instead of just running through to MAX(id) we're going to reorder the records by bantime as we go.
+cursor.execute("SELECT id FROM " + current_table + " ORDER BY bantime ASC")
+id_list = cursor.fetchall()
+start_time = datetime.now()
+print("Beginning conversion at {0}".format(start_time.strftime("%Y-%m-%d %H:%M:%S")))
+try:
+ for current_id in id_list:
+ if current_id[0] % 5000 == 0:
+ cur_time = datetime.now()
+ print("Reached row ID {0} Duration: {1}".format(current_id[0], cur_time - start_time))
+ cursor.execute("SELECT * FROM " + current_table + " WHERE id = %s", [current_id[0]])
+ query_row = cursor.fetchone()
+ if not query_row:
+ continue
+ else:
+ #TG: bans with an empty reason which were somehow created with almost every field being null or empty, we can't do much but skip this
+ if not query_row[6]:
+ continue
+ bantime = query_row[1]
+ server_ip = query_row[2]
+ server_port = query_row[3]
+ round_id = query_row[4]
+ applies_to_admins = parse_admin(query_row[5])
+ reason = query_row[6]
+ role = parse_role(query_row[5], query_row[7])
+ expiration_time = parse_datetime(query_row[5], query_row[9])
+ ckey = parse_for_empty(query_row[10])
+ computerid = parse_for_empty(query_row[11])
+ ip = parse_for_empty(query_row[12])
+ a_ckey = parse_not_null(query_row[13])
+ a_computerid = parse_not_null(query_row[14])
+ a_ip = parse_not_null(query_row[15])
+ who = query_row[16]
+ adminwho = query_row[17]
+ edits = parse_for_empty(query_row[18])
+ unbanned_datetime = parse_datetime(None, query_row[20])
+ unbanned_ckey = parse_for_empty(query_row[21])
+ unbanned_computerid = parse_for_empty(query_row[22])
+ unbanned_ip = parse_for_empty(query_row[23])
+ cursor.execute("INSERT INTO " + new_table + " (bantime, server_ip, server_port, round_id, role, expiration_time, applies_to_admins, reason, ckey, ip, computerid, a_ckey, a_ip, a_computerid, who, adminwho, edits, unbanned_datetime, unbanned_ckey, unbanned_ip, unbanned_computerid) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)", (bantime, server_ip, server_port, round_id, role, expiration_time, applies_to_admins, reason, ckey, ip, computerid, a_ckey, a_ip, a_computerid, who, adminwho, edits, unbanned_datetime, unbanned_ckey, unbanned_ip, unbanned_computerid))
+ db.commit()
+ end_time = datetime.now()
+ print("Conversion completed at {0}".format(datetime.now().strftime("%Y-%m-%d %H:%M:%S")))
+ print("Script duration: {0}".format(end_time - start_time))
+except Exception as e:
+ end_time = datetime.now()
+ print("Error encountered on row ID {0} at {1}".format(current_id[0], datetime.now().strftime("%Y-%m-%d %H:%M:%S")))
+ print("Script duration: {0}".format(end_time - start_time))
+ cursor.execute("TRUNCATE {0} ".format(new_table))
+ raise e
+cursor.close()
diff --git a/SQL/database_changelog.txt b/SQL/database_changelog.txt
index 56f05e84f1..410223a005 100644
--- a/SQL/database_changelog.txt
+++ b/SQL/database_changelog.txt
@@ -1,13 +1,256 @@
Any time you make a change to the schema files, remember to increment the database schema version. Generally increment the minor number, major should be reserved for significant changes to the schema. Both values go up to 255.
-The latest database version is 4.7; The query to update the schema revision table is:
+The latest database version is 5.12; The query to update the schema revision table is:
-INSERT INTO `schema_revision` (`major`, `minor`) VALUES (4, 7);
+INSERT INTO `schema_revision` (`major`, `minor`) VALUES (5, 12);
or
-INSERT INTO `SS13_schema_revision` (`major`, `minor`) VALUES (4, 7);
+INSERT INTO `SS13_schema_revision` (`major`, `minor`) VALUES (5, 12);
In any query remember to add a prefix to the table names if you use one.
+####################################################################################################
+NOTICE! BANS AND OTHERS ARENT SET TO THEIR LATEST FORMAT YET AS THE BACKEND DOES NOT SUPPORT IT YET!
+####################################################################################################
+
+-----------------------------------------------------
+
+Version 5.12, 29 December 2020, by Missfox
+Modified table `messages`, adding column `playtime` to show the user's playtime when the note was created.
+
+ALTER TABLE `messages` ADD `playtime` INT(11) NULL DEFAULT(NULL) AFTER `severity`
+
+-----------------------------------------------------
+
+Version 5.11, 7 September 2020, by bobbahbrown, MrStonedOne, and Jordie0608
+
+Adds indices to support search operations on the adminhelp ticket tables. This is to support improved performance on Atlanta Ned's Statbus.
+
+CREATE INDEX `idx_ticket_act_recip` (`action`, `recipient`)
+CREATE INDEX `idx_ticket_act_send` (`action`, `sender`)
+CREATE INDEX `idx_ticket_tic_rid` (`ticket`, `round_id`)
+CREATE INDEX `idx_ticket_act_time_rid` (`action`, `timestamp`, `round_id`)
+
+-----------------------------------------------------
+
+Version 5.10, 7 August 2020, by oranges
+
+Changes how the discord verification process works.
+Adds the discord_links table, and migrates discord id entries from player table to the discord links table in a once off operation and then removes the discord id
+on the player table
+
+START TRANSACTION;
+
+DROP TABLE IF EXISTS `discord_links`;
+CREATE TABLE `discord_links` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `ckey` VARCHAR(32) NOT NULL,
+ `discord_id` BIGINT(20) DEFAULT NULL,
+ `timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ `one_time_token` VARCHAR(100) NOT NULL,
+ `valid` BOOLEAN NOT NULL DEFAULT FALSE,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB;
+
+INSERT INTO `discord_links` (`ckey`, `discord_id`, `one_time_token`, `valid`) SELECT `ckey`, `discord_id`, CONCAT("presync_from_player_table_", `ckey`), TRUE FROM `player` WHERE discord_id IS NOT NULL;
+
+ALTER TABLE `player` DROP COLUMN `discord_id`;
+
+COMMIT;
+
+-----------------------------------------------------
+
+Version 5.9, 19 April 2020, by Jordie0608
+Updates and improvements to poll handling.
+Added the `deleted` column to tables 'poll_option', 'poll_textreply' and 'poll_vote' and the columns `created_datetime`, `subtitle`, `allow_revoting` and `deleted` to 'poll_question'.
+Changes table 'poll_question' column `createdby_ckey` to be NOT NULL and index `idx_pquest_time_admin` to be `idx_pquest_time_deleted_id` and 'poll_textreply' column `adminrank` to have no default.
+Added procedure `set_poll_deleted` that's called when deleting a poll to set deleted to true on each poll table where rows matching a poll_id argument.
+
+ALTER TABLE `poll_option`
+ ADD COLUMN `deleted` TINYINT(1) UNSIGNED NOT NULL DEFAULT '0' AFTER `default_percentage_calc`;
+
+ALTER TABLE `poll_question`
+ CHANGE COLUMN `createdby_ckey` `createdby_ckey` VARCHAR(32) NOT NULL AFTER `multiplechoiceoptions`,
+ ADD COLUMN `created_datetime` datetime NOT NULL AFTER `polltype`,
+ ADD COLUMN `subtitle` VARCHAR(255) NULL DEFAULT NULL AFTER `question`,
+ ADD COLUMN `allow_revoting` TINYINT(1) UNSIGNED NOT NULL AFTER `dontshow`,
+ ADD COLUMN `deleted` TINYINT(1) UNSIGNED NOT NULL DEFAULT '0' AFTER `allow_revoting`,
+ DROP INDEX `idx_pquest_time_admin`,
+ ADD INDEX `idx_pquest_time_deleted_id` (`starttime`, `endtime`, `deleted`, `id`);
+
+ALTER TABLE `poll_textreply`
+ CHANGE COLUMN `adminrank` `adminrank` varchar(32) NOT NULL AFTER `replytext`,
+ ADD COLUMN `deleted` TINYINT(1) UNSIGNED NOT NULL DEFAULT '0' AFTER `adminrank`;
+
+ALTER TABLE `poll_vote`
+ ADD COLUMN `deleted` TINYINT(1) UNSIGNED NOT NULL DEFAULT '0' AFTER `rating`;
+
+DELIMITER $$
+CREATE PROCEDURE `set_poll_deleted`(
+ IN `poll_id` INT
+)
+SQL SECURITY INVOKER
+BEGIN
+UPDATE `poll_question` SET deleted = 1 WHERE id = poll_id;
+UPDATE `poll_option` SET deleted = 1 WHERE pollid = poll_id;
+UPDATE `poll_vote` SET deleted = 1 WHERE pollid = poll_id;
+UPDATE `poll_textreply` SET deleted = 1 WHERE pollid = poll_id;
+END
+$$
+DELIMITER ;
+
+-----------------------------------------------------
+
+Version 5.8, 7 April 2020, by Jordie0608
+Modified table `messages`, adding column `deleted_ckey` to record who deleted a message.
+
+ALTER TABLE `messages` ADD COLUMN `deleted_ckey` VARCHAR(32) NULL DEFAULT NULL AFTER `deleted`;
+
+-----------------------------------------------------
+
+Version 5.7, 10 January 2020 by Atlanta-Ned
+Added ticket table for tracking ahelp tickets in the database.
+
+DROP TABLE IF EXISTS `ticket`;
+CREATE TABLE `ticket` (
+ `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+ `server_ip` int(10) unsigned NOT NULL,
+ `server_port` smallint(5) unsigned NOT NULL,
+ `round_id` int(11) unsigned NOT NULL,
+ `ticket` smallint(11) unsigned NOT NULL,
+ `action` varchar(20) NOT NULL DEFAULT 'Message',
+ `message` text NOT NULL,
+ `timestamp` datetime NOT NULL,
+ `recipient` varchar(32) DEFAULT NULL,
+ `sender` varchar(32) DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-----------------------------------------------------
+
+Version 5.6, 6 December 2019 by Anturke
+Added achievement_name and achievement_description columns to achievement_metadata table.
+
+
+ALTER TABLE `achievement_metadata` ADD COLUMN (`achievement_name` VARCHAR(64) NULL DEFAULT NULL, `achievement_description` VARCHAR(512) NULL DEFAULT NULL);
+
+-----------------------------------------------------
+
+Version 5.5, 26 October 2019 by Anturke
+Added achievement_metadata table.
+
+DROP TABLE IF EXISTS `achievement_metadata`;
+CREATE TABLE `achievement_metadata` (
+ `achievement_key` VARCHAR(32) NOT NULL,
+ `achievement_version` SMALLINT UNSIGNED NOT NULL DEFAULT 0,
+ `achievement_type` enum('achievement','score','award') NULL DEFAULT NULL,
+ PRIMARY KEY (`achievement_key`)
+) ENGINE=InnoDB;
+
+
+-----------------------------------------------------
+
+Version 5.4, 5 October 2019 by Anturke
+Added achievements table.
+See hub migration verb in _achievement_data.dm for details on migrating.
+
+CREATE TABLE `achievements` (
+ `ckey` VARCHAR(32) NOT NULL,
+ `achievement_key` VARCHAR(32) NOT NULL,
+ `value` INT NULL,
+ `last_updated` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (`ckey`,`achievement_key`)
+) ENGINE=InnoDB;
+
+----------------------------------------------------
+
+Version 5.3, 6 July 2019, by Atlanta-Ned
+Added a `feedback` column to the admin table, used for linking to individual admin feedback threads. Currently this is only used for statistics tracking tools such as Statbus and isn't used by the game.
+
+ALTER TABLE `admin` ADD `feedback` VARCHAR(255) NULL DEFAULT NULL AFTER `rank`;
+
+----------------------------------------------------
+
+Version 5.2, 30 May 2019, by AffectedArc07
+Added a field to the `player` table to track ckey and discord ID relationships
+
+ALTER TABLE `player`
+ ADD COLUMN `discord_id` BIGINT NULL DEFAULT NULL AFTER `flags`;
+----------------------------------------------------
+
+Version 5.1, 25 Feb 2018, by MrStonedOne
+Added four tables to enable storing of stickybans in the database since byond can lose them, and to enable disabling stickybans for a round without depending on a crash free round. Existing stickybans are automagically imported to the tables.
+
+CREATE TABLE `stickyban` (
+ `ckey` VARCHAR(32) NOT NULL,
+ `reason` VARCHAR(2048) NOT NULL,
+ `banning_admin` VARCHAR(32) NOT NULL,
+ `datetime` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ PRIMARY KEY (`ckey`)
+) ENGINE=InnoDB;
+
+CREATE TABLE `stickyban_matched_ckey` (
+ `stickyban` VARCHAR(32) NOT NULL,
+ `matched_ckey` VARCHAR(32) NOT NULL,
+ `first_matched` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ `last_matched` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ `exempt` TINYINT(1) NOT NULL DEFAULT '0',
+ PRIMARY KEY (`stickyban`, `matched_ckey`)
+) ENGINE=InnoDB;
+
+CREATE TABLE `stickyban_matched_ip` (
+ `stickyban` VARCHAR(32) NOT NULL,
+ `matched_ip` INT UNSIGNED NOT NULL,
+ `first_matched` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ `last_matched` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (`stickyban`, `matched_ip`)
+) ENGINE=InnoDB;
+
+CREATE TABLE `stickyban_matched_cid` (
+ `stickyban` VARCHAR(32) NOT NULL,
+ `matched_cid` VARCHAR(32) NOT NULL,
+ `first_matched` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ `last_matched` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (`stickyban`, `matched_cid`)
+) ENGINE=InnoDB;
+
+----------------------------------------------------
+
+Version 5.0, 28 October 2018, by Jordie0608
+Modified ban table to remove the need for the `bantype` column, a python script is used to migrate data to this new format.
+
+See the file 'ban_conversion_2018-10-28.py' for instructions on how to use the script.
+
+A new ban table can be created with the query:
+CREATE TABLE `ban` (
+ `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
+ `bantime` DATETIME NOT NULL,
+ `server_ip` INT(10) UNSIGNED NOT NULL,
+ `server_port` SMALLINT(5) UNSIGNED NOT NULL,
+ `round_id` INT(11) UNSIGNED NOT NULL,
+ `role` VARCHAR(32) NULL DEFAULT NULL,
+ `expiration_time` DATETIME NULL DEFAULT NULL,
+ `applies_to_admins` TINYINT(1) UNSIGNED NOT NULL DEFAULT '0',
+ `reason` VARCHAR(2048) NOT NULL,
+ `ckey` VARCHAR(32) NULL DEFAULT NULL,
+ `ip` INT(10) UNSIGNED NULL DEFAULT NULL,
+ `computerid` VARCHAR(32) NULL DEFAULT NULL,
+ `a_ckey` VARCHAR(32) NOT NULL,
+ `a_ip` INT(10) UNSIGNED NOT NULL,
+ `a_computerid` VARCHAR(32) NOT NULL,
+ `who` VARCHAR(2048) NOT NULL,
+ `adminwho` VARCHAR(2048) NOT NULL,
+ `edits` TEXT NULL DEFAULT NULL,
+ `unbanned_datetime` DATETIME NULL DEFAULT NULL,
+ `unbanned_ckey` VARCHAR(32) NULL DEFAULT NULL,
+ `unbanned_ip` INT(10) UNSIGNED NULL DEFAULT NULL,
+ `unbanned_computerid` VARCHAR(32) NULL DEFAULT NULL,
+ `unbanned_round_id` INT(11) UNSIGNED NULL DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `idx_ban_isbanned` (`ckey`,`role`,`unbanned_datetime`,`expiration_time`),
+ KEY `idx_ban_isbanned_details` (`ckey`,`ip`,`computerid`,`role`,`unbanned_datetime`,`expiration_time`),
+ KEY `idx_ban_count` (`bantime`,`a_ckey`,`applies_to_admins`,`unbanned_datetime`,`expiration_time`)
+) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+
----------------------------------------------------
Version 4.7, 18 August 2018, by CitrusGender
@@ -50,8 +293,7 @@ Added table `role_time_log` and triggers `role_timeTlogupdate`, `role_timeTlogin
CREATE TABLE `role_time_log` ( `id` BIGINT NOT NULL AUTO_INCREMENT , `ckey` VARCHAR(32) NOT NULL , `job` VARCHAR(128) NOT NULL , `delta` INT NOT NULL , `datetime` TIMESTAMP on update CURRENT_TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP , PRIMARY KEY (`id`), INDEX (`ckey`), INDEX (`job`), INDEX (`datetime`)) ENGINE = InnoDB;
-DELIMITER
-$$
+DELIMITER $$
CREATE TRIGGER `role_timeTlogupdate` AFTER UPDATE ON `role_time` FOR EACH ROW BEGIN INSERT into role_time_log (ckey, job, delta) VALUES (NEW.CKEY, NEW.job, NEW.minutes-OLD.minutes);
END
$$
@@ -61,7 +303,7 @@ $$
CREATE TRIGGER `role_timeTlogdelete` AFTER DELETE ON `role_time` FOR EACH ROW BEGIN INSERT into role_time_log (ckey, job, delta) VALUES (OLD.ckey, OLD.job, 0-OLD.minutes);
END
$$
-
+DELIMITER ;
----------------------------------------------------
Version 4.2, 17 April 2018, by Jordie0608
diff --git a/SQL/tgstation_schema.sql b/SQL/tgstation_schema.sql
index 34baaaa4c6..9a9847a372 100644
--- a/SQL/tgstation_schema.sql
+++ b/SQL/tgstation_schema.sql
@@ -19,8 +19,9 @@ DROP TABLE IF EXISTS `admin`;
CREATE TABLE `admin` (
`ckey` varchar(32) NOT NULL,
`rank` varchar(32) NOT NULL,
+ `feedback` varchar(255) DEFAULT NULL,
PRIMARY KEY (`ckey`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@@ -40,7 +41,7 @@ CREATE TABLE `admin_log` (
`target` varchar(32) NOT NULL,
`log` varchar(1000) NOT NULL,
PRIMARY KEY (`id`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@@ -56,7 +57,7 @@ CREATE TABLE `admin_ranks` (
`exclude_flags` smallint(5) unsigned NOT NULL,
`can_edit_flags` smallint(5) unsigned NOT NULL,
PRIMARY KEY (`rank`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@@ -67,11 +68,11 @@ DROP TABLE IF EXISTS `ban`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `ban` (
- `id` int(11) NOT NULL AUTO_INCREMENT,
- `bantime` datetime NOT NULL,
- `server_ip` int(10) unsigned NOT NULL,
- `server_port` smallint(5) unsigned NOT NULL,
- `round_id` int(11) NOT NULL,
+ `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
+ `bantime` DATETIME NOT NULL,
+ `server_ip` INT(10) UNSIGNED NOT NULL,
+ `server_port` SMALLINT(5) UNSIGNED NOT NULL,
+ `round_id` INT(11) UNSIGNED NOT NULL,
`bantype` enum('PERMABAN','TEMPBAN','JOB_PERMABAN','JOB_TEMPBAN','ADMIN_PERMABAN','ADMIN_TEMPBAN') NOT NULL,
`reason` varchar(2048) NOT NULL,
`job` varchar(32) DEFAULT NULL,
@@ -95,7 +96,7 @@ CREATE TABLE `ban` (
KEY `idx_ban_checkban` (`ckey`,`bantype`,`expiration_time`,`unbanned`,`job`),
KEY `idx_ban_isbanned` (`ckey`,`ip`,`computerid`,`bantype`,`expiration_time`,`unbanned`),
KEY `idx_ban_count` (`id`,`a_ckey`,`bantype`,`expiration_time`,`unbanned`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@@ -115,7 +116,7 @@ CREATE TABLE `connection_log` (
`ip` int(10) unsigned NOT NULL,
`computerid` varchar(45) DEFAULT NULL,
PRIMARY KEY (`id`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@@ -152,7 +153,7 @@ CREATE TABLE `death` (
`last_words` varchar(255) DEFAULT NULL,
`suicide` tinyint(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@@ -171,7 +172,7 @@ CREATE TABLE `feedback` (
`version` tinyint(3) unsigned NOT NULL,
`json` json NOT NULL,
PRIMARY KEY (`id`)
-) ENGINE=MyISAM DEFAULT CHARSET=latin1;
+) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@@ -187,7 +188,7 @@ CREATE TABLE `ipintel` (
`intel` double NOT NULL DEFAULT '0',
PRIMARY KEY (`ip`),
KEY `idx_ipintel` (`ip`,`intel`,`date`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@@ -206,7 +207,7 @@ CREATE TABLE `legacy_population` (
`server_port` smallint(5) unsigned NOT NULL,
`round_id` int(11) unsigned NOT NULL,
PRIMARY KEY (`id`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@@ -231,7 +232,7 @@ CREATE TABLE `library` (
KEY `idx_lib_id_del` (`id`,`deleted`),
KEY `idx_lib_del_title` (`deleted`,`title`),
KEY `idx_lib_search` (`deleted`,`author`,`title`,`category`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@@ -255,14 +256,16 @@ CREATE TABLE `messages` (
`secret` tinyint(1) unsigned NOT NULL,
`expire_timestamp` datetime DEFAULT NULL,
`severity` enum('high','medium','minor','none') DEFAULT NULL,
+ `playtime` int(11) unsigned NULL DEFAULT NULL,
`lasteditor` varchar(32) DEFAULT NULL,
`edits` text,
`deleted` tinyint(1) unsigned NOT NULL DEFAULT '0',
+ `deleted_ckey` VARCHAR(32) NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_msg_ckey_time` (`targetckey`,`timestamp`, `deleted`),
KEY `idx_msg_type_ckeys_time` (`type`,`targetckey`,`adminckey`,`timestamp`, `deleted`),
KEY `idx_msg_type_ckey_time_odr` (`type`,`targetckey`,`timestamp`, `deleted`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@@ -299,7 +302,7 @@ CREATE TABLE IF NOT EXISTS `role_time_log` (
KEY `ckey` (`ckey`),
KEY `job` (`job`),
KEY `datetime` (`datetime`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@@ -324,7 +327,7 @@ CREATE TABLE `player` (
PRIMARY KEY (`ckey`),
KEY `idx_player_cid_ckey` (`computerid`,`ckey`),
KEY `idx_player_ip_ckey` (`ip`,`ckey`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@@ -344,9 +347,10 @@ CREATE TABLE `poll_option` (
`descmid` varchar(32) DEFAULT NULL,
`descmax` varchar(32) DEFAULT NULL,
`default_percentage_calc` tinyint(1) unsigned NOT NULL DEFAULT '1',
+ `deleted` tinyint(1) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `idx_pop_pollid` (`pollid`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@@ -359,19 +363,23 @@ DROP TABLE IF EXISTS `poll_question`;
CREATE TABLE `poll_question` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`polltype` enum('OPTION','TEXT','NUMVAL','MULTICHOICE','IRV') NOT NULL,
+ `created_datetime` datetime NOT NULL,
`starttime` datetime NOT NULL,
`endtime` datetime NOT NULL,
`question` varchar(255) NOT NULL,
+ `subtitle` varchar(255) DEFAULT NULL,
`adminonly` tinyint(1) unsigned NOT NULL,
`multiplechoiceoptions` int(2) DEFAULT NULL,
- `createdby_ckey` varchar(32) DEFAULT NULL,
+ `createdby_ckey` varchar(32) NOT NULL,
`createdby_ip` int(10) unsigned NOT NULL,
`dontshow` tinyint(1) unsigned NOT NULL,
+ `allow_revoting` tinyint(1) unsigned NOT NULL,
+ `deleted` tinyint(1) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `idx_pquest_question_time_ckey` (`question`,`starttime`,`endtime`,`createdby_ckey`,`createdby_ip`),
- KEY `idx_pquest_time_admin` (`starttime`,`endtime`,`adminonly`),
+ KEY `idx_pquest_time_deleted_id` (`starttime`,`endtime`, `deleted`, `id`),
KEY `idx_pquest_id_time_type_admin` (`id`,`starttime`,`endtime`,`polltype`,`adminonly`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@@ -388,10 +396,11 @@ CREATE TABLE `poll_textreply` (
`ckey` varchar(32) NOT NULL,
`ip` int(10) unsigned NOT NULL,
`replytext` varchar(2048) NOT NULL,
- `adminrank` varchar(32) NOT NULL DEFAULT 'Player',
+ `adminrank` varchar(32) NOT NULL,
+ `deleted` tinyint(1) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `idx_ptext_pollid_ckey` (`pollid`,`ckey`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@@ -410,10 +419,11 @@ CREATE TABLE `poll_vote` (
`ip` int(10) unsigned NOT NULL,
`adminrank` varchar(32) NOT NULL,
`rating` int(2) DEFAULT NULL,
+ `deleted` tinyint(1) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `idx_pvote_pollid_ckey` (`pollid`,`ckey`),
KEY `idx_pvote_optionid_ckey` (`optionid`,`ckey`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@@ -438,7 +448,7 @@ CREATE TABLE `round` (
`map_name` VARCHAR(32) NULL,
`station_name` VARCHAR(80) NULL,
PRIMARY KEY (`id`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
@@ -451,9 +461,113 @@ CREATE TABLE `schema_revision` (
`minor` TINYINT(3) unsigned NOT NULL,
`date` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`major`, `minor`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+
+--
+-- Table structure for table `stickyban`
+--
+DROP TABLE IF EXISTS `stickyban`;
+CREATE TABLE `stickyban` (
+ `ckey` VARCHAR(32) NOT NULL,
+ `reason` VARCHAR(2048) NOT NULL,
+ `banning_admin` VARCHAR(32) NOT NULL,
+ `datetime` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ PRIMARY KEY (`ckey`)
+) ENGINE=InnoDB;
+
+--
+-- Table structure for table `stickyban_matched_ckey`
+--
+DROP TABLE IF EXISTS `stickyban_matched_ckey`;
+CREATE TABLE `stickyban_matched_ckey` (
+ `stickyban` VARCHAR(32) NOT NULL,
+ `matched_ckey` VARCHAR(32) NOT NULL,
+ `first_matched` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ `last_matched` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ `exempt` TINYINT(1) NOT NULL DEFAULT '0',
+ PRIMARY KEY (`stickyban`, `matched_ckey`)
+) ENGINE=InnoDB;
+
+--
+-- Table structure for table `stickyban_matched_ip`
+--
+DROP TABLE IF EXISTS `stickyban_matched_ip`;
+CREATE TABLE `stickyban_matched_ip` (
+ `stickyban` VARCHAR(32) NOT NULL,
+ `matched_ip` INT UNSIGNED NOT NULL,
+ `first_matched` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ `last_matched` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (`stickyban`, `matched_ip`)
+) ENGINE=InnoDB;
+
+--
+-- Table structure for table `stickyban_matched_cid`
+--
+DROP TABLE IF EXISTS `stickyban_matched_cid`;
+CREATE TABLE `stickyban_matched_cid` (
+ `stickyban` VARCHAR(32) NOT NULL,
+ `matched_cid` VARCHAR(32) NOT NULL,
+ `first_matched` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ `last_matched` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (`stickyban`, `matched_cid`)
+) ENGINE=InnoDB;
+
+--
+-- Table structure for table `achievements`
+--
+DROP TABLE IF EXISTS `achievements`;
+CREATE TABLE `achievements` (
+ `ckey` VARCHAR(32) NOT NULL,
+ `achievement_key` VARCHAR(32) NOT NULL,
+ `value` INT NULL,
+ `last_updated` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (`ckey`,`achievement_key`)
+) ENGINE=InnoDB;
+
+DROP TABLE IF EXISTS `achievement_metadata`;
+CREATE TABLE `achievement_metadata` (
+ `achievement_key` VARCHAR(32) NOT NULL,
+ `achievement_version` SMALLINT UNSIGNED NOT NULL DEFAULT 0,
+ `achievement_type` enum('achievement','score','award') NULL DEFAULT NULL,
+ `achievement_name` VARCHAR(64) NULL DEFAULT NULL,
+ `achievement_description` VARCHAR(512) NULL DEFAULT NULL,
+ PRIMARY KEY (`achievement_key`)
+) ENGINE=InnoDB;
+
+--
+-- Table structure for table `ticket`
+--
+DROP TABLE IF EXISTS `ticket`;
+CREATE TABLE `ticket` (
+ `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+ `server_ip` int(10) unsigned NOT NULL,
+ `server_port` smallint(5) unsigned NOT NULL,
+ `round_id` int(11) unsigned NOT NULL,
+ `ticket` smallint(11) unsigned NOT NULL,
+ `action` varchar(20) NOT NULL DEFAULT 'Message',
+ `message` text NOT NULL,
+ `timestamp` datetime NOT NULL,
+ `recipient` varchar(32) DEFAULT NULL,
+ `sender` varchar(32) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `idx_ticket_act_recip` (`action`, `recipient`),
+ KEY `idx_ticket_act_send` (`action`, `sender`),
+ KEY `idx_ticket_tic_rid` (`ticket`, `round_id`),
+ KEY `idx_ticket_act_time_rid` (`action`, `timestamp`, `round_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
DELIMITER $$
+CREATE PROCEDURE `set_poll_deleted`(
+ IN `poll_id` INT
+)
+SQL SECURITY INVOKER
+BEGIN
+UPDATE `poll_question` SET deleted = 1 WHERE id = poll_id;
+UPDATE `poll_option` SET deleted = 1 WHERE pollid = poll_id;
+UPDATE `poll_vote` SET deleted = 1 WHERE pollid = poll_id;
+UPDATE `poll_textreply` SET deleted = 1 WHERE pollid = poll_id;
+END
+$$
CREATE TRIGGER `role_timeTlogupdate` AFTER UPDATE ON `role_time` FOR EACH ROW BEGIN INSERT into role_time_log (ckey, job, delta) VALUES (NEW.CKEY, NEW.job, NEW.minutes-OLD.minutes);
END
$$
@@ -463,6 +577,21 @@ $$
CREATE TRIGGER `role_timeTlogdelete` AFTER DELETE ON `role_time` FOR EACH ROW BEGIN INSERT into role_time_log (ckey, job, delta) VALUES (OLD.ckey, OLD.job, 0-OLD.minutes);
END
$$
+DELIMITER ;
+
+--
+-- Table structure for table `discord_links`
+--
+DROP TABLE IF EXISTS `discord_links`;
+CREATE TABLE `discord_links` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `ckey` VARCHAR(32) NOT NULL,
+ `discord_id` BIGINT(20) DEFAULT NULL,
+ `timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ `one_time_token` VARCHAR(100) NOT NULL,
+ `valid` BOOLEAN NOT NULL DEFAULT FALSE,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
diff --git a/SQL/tgstation_schema_prefixed.sql b/SQL/tgstation_schema_prefixed.sql
index 5cb57a9582..654f45f4f2 100644
--- a/SQL/tgstation_schema_prefixed.sql
+++ b/SQL/tgstation_schema_prefixed.sql
@@ -19,8 +19,9 @@ DROP TABLE IF EXISTS `SS13_admin`;
CREATE TABLE `SS13_admin` (
`ckey` varchar(32) NOT NULL,
`rank` varchar(32) NOT NULL,
+ `feedback` varchar(255) DEFAULT NULL,
PRIMARY KEY (`ckey`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@@ -40,7 +41,7 @@ CREATE TABLE `SS13_admin_log` (
`target` varchar(32) NOT NULL,
`log` varchar(1000) NOT NULL,
PRIMARY KEY (`id`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@@ -56,7 +57,7 @@ CREATE TABLE `SS13_admin_ranks` (
`exclude_flags` smallint(5) unsigned NOT NULL,
`can_edit_flags` smallint(5) unsigned NOT NULL,
PRIMARY KEY (`rank`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@@ -67,11 +68,11 @@ DROP TABLE IF EXISTS `SS13_ban`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `SS13_ban` (
- `id` int(11) NOT NULL AUTO_INCREMENT,
- `bantime` datetime NOT NULL,
- `server_ip` int(10) unsigned NOT NULL,
- `server_port` smallint(5) unsigned NOT NULL,
- `round_id` int(11) NOT NULL,
+ `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
+ `bantime` DATETIME NOT NULL,
+ `server_ip` INT(10) UNSIGNED NOT NULL,
+ `server_port` SMALLINT(5) UNSIGNED NOT NULL,
+ `round_id` INT(11) UNSIGNED NOT NULL,
`bantype` enum('PERMABAN','TEMPBAN','JOB_PERMABAN','JOB_TEMPBAN','ADMIN_PERMABAN','ADMIN_TEMPBAN') NOT NULL,
`reason` varchar(2048) NOT NULL,
`job` varchar(32) DEFAULT NULL,
@@ -95,7 +96,7 @@ CREATE TABLE `SS13_ban` (
KEY `idx_ban_checkban` (`ckey`,`bantype`,`expiration_time`,`unbanned`,`job`),
KEY `idx_ban_isbanned` (`ckey`,`ip`,`computerid`,`bantype`,`expiration_time`,`unbanned`),
KEY `idx_ban_count` (`id`,`a_ckey`,`bantype`,`expiration_time`,`unbanned`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@@ -115,7 +116,7 @@ CREATE TABLE `SS13_connection_log` (
`ip` int(10) unsigned NOT NULL,
`computerid` varchar(45) DEFAULT NULL,
PRIMARY KEY (`id`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@@ -152,7 +153,7 @@ CREATE TABLE `SS13_death` (
`last_words` varchar(255) DEFAULT NULL,
`suicide` tinyint(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@@ -171,7 +172,7 @@ CREATE TABLE `SS13_feedback` (
`key_type` enum('text', 'amount', 'tally', 'nested tally', 'associative') NOT NULL,
`json` json NOT NULL,
PRIMARY KEY (`id`)
-) ENGINE=MyISAM DEFAULT CHARSET=latin1;
+) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@@ -187,7 +188,7 @@ CREATE TABLE `SS13_ipintel` (
`intel` double NOT NULL DEFAULT '0',
PRIMARY KEY (`ip`),
KEY `idx_ipintel` (`ip`,`intel`,`date`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@@ -206,7 +207,7 @@ CREATE TABLE `SS13_legacy_population` (
`server_port` smallint(5) unsigned NOT NULL,
`round_id` int(11) unsigned NOT NULL,
PRIMARY KEY (`id`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@@ -231,7 +232,7 @@ CREATE TABLE `SS13_library` (
KEY `idx_lib_id_del` (`id`,`deleted`),
KEY `idx_lib_del_title` (`deleted`,`title`),
KEY `idx_lib_search` (`deleted`,`author`,`title`,`category`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@@ -255,14 +256,16 @@ CREATE TABLE `SS13_messages` (
`secret` tinyint(1) unsigned NOT NULL,
`expire_timestamp` datetime DEFAULT NULL,
`severity` enum('high','medium','minor','none') DEFAULT NULL,
+ `playtime` int(11) unsigned NULL DEFAULT NULL,
`lasteditor` varchar(32) DEFAULT NULL,
`edits` text,
`deleted` tinyint(1) unsigned NOT NULL DEFAULT '0',
+ `deleted_ckey` VARCHAR(32) NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_msg_ckey_time` (`targetckey`,`timestamp`, `deleted`),
KEY `idx_msg_type_ckeys_time` (`type`,`targetckey`,`adminckey`,`timestamp`, `deleted`),
KEY `idx_msg_type_ckey_time_odr` (`type`,`targetckey`,`timestamp`, `deleted`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@@ -299,7 +302,7 @@ CREATE TABLE IF NOT EXISTS `SS13_role_time_log` (
KEY `ckey` (`ckey`),
KEY `job` (`job`),
KEY `datetime` (`datetime`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@@ -324,7 +327,7 @@ CREATE TABLE `SS13_player` (
PRIMARY KEY (`ckey`),
KEY `idx_player_cid_ckey` (`computerid`,`ckey`),
KEY `idx_player_ip_ckey` (`ip`,`ckey`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@@ -344,9 +347,10 @@ CREATE TABLE `SS13_poll_option` (
`descmid` varchar(32) DEFAULT NULL,
`descmax` varchar(32) DEFAULT NULL,
`default_percentage_calc` tinyint(1) unsigned NOT NULL DEFAULT '1',
+ `deleted` tinyint(1) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `idx_pop_pollid` (`pollid`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@@ -359,19 +363,23 @@ DROP TABLE IF EXISTS `SS13_poll_question`;
CREATE TABLE `SS13_poll_question` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`polltype` enum('OPTION','TEXT','NUMVAL','MULTICHOICE','IRV') NOT NULL,
+ `created_datetime` datetime NOT NULL,
`starttime` datetime NOT NULL,
`endtime` datetime NOT NULL,
`question` varchar(255) NOT NULL,
+ `subtitle` varchar(255) DEFAULT NULL,
`adminonly` tinyint(1) unsigned NOT NULL,
`multiplechoiceoptions` int(2) DEFAULT NULL,
- `createdby_ckey` varchar(32) DEFAULT NULL,
+ `createdby_ckey` varchar(32) NOT NULL,
`createdby_ip` int(10) unsigned NOT NULL,
`dontshow` tinyint(1) unsigned NOT NULL,
+ `allow_revoting` tinyint(1) unsigned NOT NULL,
+ `deleted` tinyint(1) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `idx_pquest_question_time_ckey` (`question`,`starttime`,`endtime`,`createdby_ckey`,`createdby_ip`),
- KEY `idx_pquest_time_admin` (`starttime`,`endtime`,`adminonly`),
+ KEY `idx_pquest_time_deleted_id` (`starttime`,`endtime`, `deleted`, `id`),
KEY `idx_pquest_id_time_type_admin` (`id`,`starttime`,`endtime`,`polltype`,`adminonly`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@@ -388,10 +396,11 @@ CREATE TABLE `SS13_poll_textreply` (
`ckey` varchar(32) NOT NULL,
`ip` int(10) unsigned NOT NULL,
`replytext` varchar(2048) NOT NULL,
- `adminrank` varchar(32) NOT NULL DEFAULT 'Player',
+ `adminrank` varchar(32) NOT NULL,
+ `deleted` tinyint(1) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `idx_ptext_pollid_ckey` (`pollid`,`ckey`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@@ -410,10 +419,11 @@ CREATE TABLE `SS13_poll_vote` (
`ip` int(10) unsigned NOT NULL,
`adminrank` varchar(32) NOT NULL,
`rating` int(2) DEFAULT NULL,
+ `deleted` tinyint(1) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `idx_pvote_pollid_ckey` (`pollid`,`ckey`),
KEY `idx_pvote_optionid_ckey` (`optionid`,`ckey`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@@ -438,7 +448,7 @@ CREATE TABLE `SS13_round` (
`map_name` VARCHAR(32) NULL,
`station_name` VARCHAR(80) NULL,
PRIMARY KEY (`id`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
@@ -451,9 +461,113 @@ CREATE TABLE `SS13_schema_revision` (
`minor` TINYINT(3) unsigned NOT NULL,
`date` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`major`,`minor`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+
+--
+-- Table structure for table `SS13_stickyban`
+--
+DROP TABLE IF EXISTS `SS13_stickyban`;
+CREATE TABLE `SS13_stickyban` (
+ `ckey` VARCHAR(32) NOT NULL,
+ `reason` VARCHAR(2048) NOT NULL,
+ `banning_admin` VARCHAR(32) NOT NULL,
+ `datetime` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ PRIMARY KEY (`ckey`)
+) ENGINE=InnoDB;
+
+--
+-- Table structure for table `SS13_stickyban_matched_ckey`
+--
+DROP TABLE IF EXISTS `SS13_stickyban_matched_ckey`;
+CREATE TABLE `SS13_stickyban_matched_ckey` (
+ `stickyban` VARCHAR(32) NOT NULL,
+ `matched_ckey` VARCHAR(32) NOT NULL,
+ `first_matched` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ `last_matched` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ `exempt` TINYINT(1) NOT NULL DEFAULT '0',
+ PRIMARY KEY (`stickyban`, `matched_ckey`)
+) ENGINE=InnoDB;
+
+--
+-- Table structure for table `SS13_stickyban_matched_ip`
+--
+DROP TABLE IF EXISTS `SS13_stickyban_matched_ip`;
+CREATE TABLE `SS13_stickyban_matched_ip` (
+ `stickyban` VARCHAR(32) NOT NULL,
+ `matched_ip` INT UNSIGNED NOT NULL,
+ `first_matched` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ `last_matched` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (`stickyban`, `matched_ip`)
+) ENGINE=InnoDB;
+
+--
+-- Table structure for table `SS13_stickyban_matched_cid`
+--
+DROP TABLE IF EXISTS `SS13_stickyban_matched_cid`;
+CREATE TABLE `SS13_stickyban_matched_cid` (
+ `stickyban` VARCHAR(32) NOT NULL,
+ `matched_cid` VARCHAR(32) NOT NULL,
+ `first_matched` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ `last_matched` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (`stickyban`, `matched_cid`)
+) ENGINE=InnoDB;
+
+--
+-- Table structure for table `SS13_achievements`
+--
+DROP TABLE IF EXISTS `SS13_achievements`;
+CREATE TABLE `SS13_achievements` (
+ `ckey` VARCHAR(32) NOT NULL,
+ `achievement_key` VARCHAR(32) NOT NULL,
+ `value` INT NULL,
+ `last_updated` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (`ckey`,`achievement_key`)
+) ENGINE=InnoDB;
+
+DROP TABLE IF EXISTS `SS13_achievement_metadata`;
+CREATE TABLE `SS13_achievement_metadata` (
+ `achievement_key` VARCHAR(32) NOT NULL,
+ `achievement_version` SMALLINT UNSIGNED NOT NULL DEFAULT 0,
+ `achievement_type` enum('achievement','score','award') NULL DEFAULT NULL,
+ `achievement_name` VARCHAR(64) NULL DEFAULT NULL,
+ `achievement_description` VARCHAR(512) NULL DEFAULT NULL,
+ PRIMARY KEY (`achievement_key`)
+) ENGINE=InnoDB;
+
+--
+-- Table structure for table `SS13_ticket`
+--
+DROP TABLE IF EXISTS `SS13_ticket`;
+CREATE TABLE `SS13_ticket` (
+ `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+ `server_ip` int(10) unsigned NOT NULL,
+ `server_port` smallint(5) unsigned NOT NULL,
+ `round_id` int(11) unsigned NOT NULL,
+ `ticket` smallint(11) unsigned NOT NULL,
+ `action` varchar(20) NOT NULL DEFAULT 'Message',
+ `message` text NOT NULL,
+ `timestamp` datetime NOT NULL,
+ `recipient` varchar(32) DEFAULT NULL,
+ `sender` varchar(32) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `idx_ticket_act_recip` (`action`, `recipient`),
+ KEY `idx_ticket_act_send` (`action`, `sender`),
+ KEY `idx_ticket_tic_rid` (`ticket`, `round_id`),
+ KEY `idx_ticket_act_time_rid` (`action`, `timestamp`, `round_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
DELIMITER $$
+CREATE PROCEDURE `set_poll_deleted`(
+ IN `poll_id` INT
+)
+SQL SECURITY INVOKER
+BEGIN
+UPDATE `SS13_poll_question` SET deleted = 1 WHERE id = poll_id;
+UPDATE `SS13_poll_option` SET deleted = 1 WHERE pollid = poll_id;
+UPDATE `SS13_poll_vote` SET deleted = 1 WHERE pollid = poll_id;
+UPDATE `SS13_poll_textreply` SET deleted = 1 WHERE pollid = poll_id;
+END
+$$
CREATE TRIGGER `SS13_role_timeTlogupdate` AFTER UPDATE ON `SS13_role_time` FOR EACH ROW BEGIN INSERT into SS13_role_time_log (ckey, job, delta) VALUES (NEW.CKEY, NEW.job, NEW.minutes-OLD.minutes);
END
$$
@@ -463,6 +577,21 @@ $$
CREATE TRIGGER `SS13_role_timeTlogdelete` AFTER DELETE ON `SS13_role_time` FOR EACH ROW BEGIN INSERT into SS13_role_time_log (ckey, job, delta) VALUES (OLD.ckey, OLD.job, 0-OLD.minutes);
END
$$
+DELIMITER ;
+
+--
+-- Table structure for table `discord_links`
+--
+DROP TABLE IF EXISTS `SS13_discord_links`;
+CREATE TABLE `SS13_discord_links` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `ckey` VARCHAR(32) NOT NULL,
+ `discord_id` BIGINT(20) DEFAULT NULL,
+ `timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ `one_time_token` VARCHAR(100) NOT NULL,
+ `valid` BOOLEAN NOT NULL DEFAULT FALSE,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
diff --git a/SpacemanDMM.toml b/SpacemanDMM.toml
index b827472254..81aff0d557 100644
--- a/SpacemanDMM.toml
+++ b/SpacemanDMM.toml
@@ -1,2 +1,9 @@
[langserver]
dreamchecker = true
+
+[code_standards]
+disallow_relative_type_definitions = true
+disallow_relative_proc_definitions = true
+
+[dmdoc]
+use_typepath_names = true
diff --git a/TGS3.json b/TGS3.json
index 39b75bd913..228854a166 100644
--- a/TGS3.json
+++ b/TGS3.json
@@ -1,22 +1,9 @@
{
"documentation": "/tg/station server 3 configuration file",
- "changelog": {
- "script": "tools/ss13_genchangelog.py",
- "arguments": "html/changelog.html html/changelogs",
- "pip_dependancies": [
- "PyYaml",
- "beautifulsoup4"
- ]
- },
- "synchronize_paths": [
- "html/changelog.html",
- "html/changelogs/*"
- ],
+ "synchronize_paths": [],
"static_directories": [
"config",
"data"
],
- "dlls": [
- "libmariadb.dll"
- ]
+ "dlls": []
}
diff --git a/_maps/RandomRuins/AnywhereRuins/golem_ship.dmm b/_maps/RandomRuins/AnywhereRuins/golem_ship.dmm
index 37a89ba2ff..e20fa042d7 100644
--- a/_maps/RandomRuins/AnywhereRuins/golem_ship.dmm
+++ b/_maps/RandomRuins/AnywhereRuins/golem_ship.dmm
@@ -18,7 +18,7 @@
/obj/item/mining_scanner,
/obj/item/flashlight/lantern,
/obj/item/card/id/mining,
-/obj/item/gps/mining,
+/obj/item/gps/mining/off,
/turf/open/floor/plating,
/area/ruin/powered/golem_ship)
"d" = (
@@ -32,7 +32,7 @@
/obj/item/mining_scanner,
/obj/item/flashlight/lantern,
/obj/item/card/id/mining,
-/obj/item/gps/mining,
+/obj/item/gps/mining/off,
/turf/open/floor/plating,
/area/ruin/powered/golem_ship)
"e" = (
@@ -71,8 +71,8 @@
/area/ruin/powered/golem_ship)
"k" = (
/obj/machinery/computer/arcade/battle{
- icon_state = "arcade";
- dir = 4
+ dir = 4;
+ icon_state = "arcade"
},
/turf/open/floor/mineral/titanium/purple,
/area/ruin/powered/golem_ship)
@@ -112,8 +112,8 @@
/area/ruin/powered/golem_ship)
"s" = (
/obj/machinery/computer/arcade/orion_trail{
- icon_state = "arcade";
- dir = 4
+ dir = 4;
+ icon_state = "arcade"
},
/turf/open/floor/mineral/titanium/purple,
/area/ruin/powered/golem_ship)
@@ -139,6 +139,7 @@
dir = 4;
name = "shrine of the liberator"
},
+/obj/item/tcg_card/special/golem,
/turf/open/floor/mineral/titanium/purple,
/area/ruin/powered/golem_ship)
"v" = (
diff --git a/_maps/RandomRuins/IceRuins/icemoon_surface_hotsprings.dmm b/_maps/RandomRuins/IceRuins/icemoon_surface_hotsprings.dmm
index 93b221c446..a32c526ec4 100644
--- a/_maps/RandomRuins/IceRuins/icemoon_surface_hotsprings.dmm
+++ b/_maps/RandomRuins/IceRuins/icemoon_surface_hotsprings.dmm
@@ -14,6 +14,10 @@
},
/turf/open/floor/plating/beach/sand,
/area/icemoon/surface/outdoors)
+"l" = (
+/obj/item/tcg_card/special/morph,
+/turf/open/floor/plating/beach/sand,
+/area/icemoon/surface/outdoors)
"I" = (
/turf/closed/indestructible/fakeglass,
/area/icemoon/surface/outdoors)
@@ -72,7 +76,7 @@ L
c
c
c
-U
+l
U
a
b
diff --git a/_maps/RandomRuins/IceRuins/icemoon_surface_lust.dmm b/_maps/RandomRuins/IceRuins/icemoon_surface_lust.dmm
index 704ac63a7d..7a41256478 100644
--- a/_maps/RandomRuins/IceRuins/icemoon_surface_lust.dmm
+++ b/_maps/RandomRuins/IceRuins/icemoon_surface_lust.dmm
@@ -20,6 +20,10 @@
},
/turf/open/floor/mineral/diamond,
/area/icemoon/surface/outdoors)
+"f" = (
+/obj/item/tcg_card/special/xenomaid,
+/turf/open/floor/mineral/diamond,
+/area/icemoon/surface/outdoors)
(1,1,1) = {"
a
@@ -65,7 +69,7 @@ a
a
a
b
-c
+f
c
c
b
diff --git a/_maps/RandomRuins/IceRuins/icemoon_surface_mining_site.dmm b/_maps/RandomRuins/IceRuins/icemoon_surface_mining_site.dmm
index 23abc4b731..9ea8747846 100644
--- a/_maps/RandomRuins/IceRuins/icemoon_surface_mining_site.dmm
+++ b/_maps/RandomRuins/IceRuins/icemoon_surface_mining_site.dmm
@@ -49,6 +49,7 @@
/area/ruin/unpowered)
"k" = (
/obj/structure/closet/crate/freezer,
+/obj/item/tcg_card/special/demonic_miner,
/turf/open/floor/wood,
/area/ruin/unpowered)
"l" = (
diff --git a/_maps/RandomRuins/IceRuins/icemoon_underground_wendigo_cave.dmm b/_maps/RandomRuins/IceRuins/icemoon_underground_wendigo_cave.dmm
index a383c2e8c4..5861309eff 100644
--- a/_maps/RandomRuins/IceRuins/icemoon_underground_wendigo_cave.dmm
+++ b/_maps/RandomRuins/IceRuins/icemoon_underground_wendigo_cave.dmm
@@ -31,6 +31,10 @@
"N" = (
/turf/open/indestructible/necropolis/ice,
/area/icemoon/underground/explored)
+"S" = (
+/obj/item/tcg_card/special/wendigo,
+/turf/open/indestructible/necropolis/ice,
+/area/icemoon/underground/explored)
"U" = (
/obj/item/paper/crumpled/bloody{
info = "for your own sake, do not enter"
@@ -451,7 +455,7 @@ a
N
N
N
-N
+S
N
N
N
diff --git a/_maps/RandomRuins/LavaRuins/lavaland_biodome_clown_planet.dmm b/_maps/RandomRuins/LavaRuins/lavaland_biodome_clown_planet.dmm
index 639d63772c..cd512e44b6 100644
--- a/_maps/RandomRuins/LavaRuins/lavaland_biodome_clown_planet.dmm
+++ b/_maps/RandomRuins/LavaRuins/lavaland_biodome_clown_planet.dmm
@@ -862,6 +862,7 @@
/obj/structure/disposalpipe/segment{
dir = 10
},
+/obj/item/tcg_card/special/honk,
/turf/open/floor/plating,
/area/ruin/powered/clownplanet)
"bF" = (
diff --git a/_maps/RandomRuins/LavaRuins/lavaland_surface_alien_nest.dmm b/_maps/RandomRuins/LavaRuins/lavaland_surface_alien_nest.dmm
index eab08815f3..c2bdea157e 100644
--- a/_maps/RandomRuins/LavaRuins/lavaland_surface_alien_nest.dmm
+++ b/_maps/RandomRuins/LavaRuins/lavaland_surface_alien_nest.dmm
@@ -111,6 +111,17 @@
/obj/structure/alien/resin/membrane,
/turf/open/floor/plating/asteroid/basalt/lava_land_surface,
/area/ruin/unpowered/xenonest)
+"ax" = (
+/obj/structure/alien/weeds,
+/obj/effect/decal/cleanable/blood/gibs,
+/obj/structure/alien/weeds/node,
+/mob/living/simple_animal/hostile/alien/drone,
+/turf/open/floor/plating/asteroid/basalt/lava_land_surface,
+/area/ruin/unpowered/xenonest)
+"ay" = (
+/obj/structure/stone_tile/slab/cracked,
+/turf/open/indestructible/boss,
+/area/ruin/unpowered/xenonest)
"aA" = (
/obj/structure/alien/weeds,
/obj/item/flamethrower,
@@ -130,6 +141,17 @@
},
/turf/open/floor/plating/asteroid/basalt/lava_land_surface,
/area/ruin/unpowered/xenonest)
+"aD" = (
+/obj/structure/stone_tile/surrounding_tile{
+ dir = 1
+ },
+/obj/structure/stone_tile/surrounding_tile,
+/obj/structure/stone_tile/surrounding_tile/cracked{
+ dir = 4
+ },
+/obj/structure/stone_tile/center,
+/turf/open/chasm/lavaland,
+/area/ruin/unpowered/xenonest)
"aE" = (
/obj/structure/alien/weeds,
/obj/effect/decal/cleanable/ash,
@@ -160,6 +182,19 @@
/obj/effect/decal/cleanable/blood,
/turf/open/floor/plating/asteroid/basalt/lava_land_surface,
/area/ruin/unpowered/xenonest)
+"aK" = (
+/obj/structure/stone_tile/surrounding_tile/cracked{
+ dir = 4
+ },
+/obj/structure/stone_tile/surrounding_tile/cracked{
+ dir = 8
+ },
+/obj/structure/stone_tile/surrounding_tile/cracked{
+ dir = 1
+ },
+/obj/structure/stone_tile/center/cracked,
+/turf/open/chasm/lavaland,
+/area/ruin/unpowered/xenonest)
"aL" = (
/obj/structure/alien/weeds,
/obj/item/tank/internals/plasma,
@@ -170,6 +205,26 @@
/obj/effect/decal/cleanable/blood/xeno,
/turf/open/floor/plating/asteroid/basalt/lava_land_surface,
/area/ruin/unpowered/xenonest)
+"aN" = (
+/obj/structure/stone_tile/block{
+ dir = 8
+ },
+/obj/structure/stone_tile{
+ dir = 1
+ },
+/obj/structure/stone_tile/cracked,
+/turf/open/indestructible/boss,
+/area/ruin/unpowered/xenonest)
+"aO" = (
+/obj/structure/alien/weeds,
+/obj/effect/mob_spawn/alien/corpse/humanoid/sentinel,
+/turf/open/floor/plating/asteroid/basalt/lava_land_surface,
+/area/ruin/unpowered/xenonest)
+"aP" = (
+/obj/structure/alien/weeds,
+/obj/effect/mob_spawn/alien/corpse/humanoid/hunter,
+/turf/open/floor/plating/asteroid/basalt/lava_land_surface,
+/area/ruin/unpowered/xenonest)
"aQ" = (
/obj/structure/alien/weeds,
/obj/structure/bed/nest,
@@ -215,13 +270,6 @@
/obj/effect/gibspawner/xeno,
/turf/open/floor/plating/asteroid/basalt/lava_land_surface,
/area/ruin/unpowered/xenonest)
-"aY" = (
-/obj/structure/alien/weeds,
-/obj/effect/decal/cleanable/blood/gibs,
-/mob/living/simple_animal/hostile/alien/drone,
-/obj/structure/alien/weeds/node,
-/turf/open/floor/plating/asteroid/basalt/lava_land_surface,
-/area/ruin/unpowered/xenonest)
"aZ" = (
/turf/closed/indestructible/riveted/boss,
/area/ruin/unpowered/xenonest)
@@ -338,10 +386,6 @@
/obj/structure/stone_tile,
/turf/open/indestructible/boss,
/area/ruin/unpowered/xenonest)
-"bm" = (
-/obj/structure/stone_tile/slab/cracked,
-/turf/open/indestructible/boss,
-/area/ruin/unpowered/xenonest)
"bn" = (
/obj/structure/stone_tile/block{
dir = 4
@@ -365,12 +409,6 @@
/obj/structure/stone_tile/center,
/turf/open/chasm/lavaland,
/area/ruin/unpowered/xenonest)
-"bp" = (
-/obj/structure/stone_tile/block{
- dir = 4
- },
-/turf/open/chasm/lavaland,
-/area/ruin/unpowered/xenonest)
"bq" = (
/turf/open/chasm/lavaland,
/area/ruin/unpowered/xenonest)
@@ -391,33 +429,6 @@
},
/turf/open/chasm/lavaland,
/area/ruin/unpowered/xenonest)
-"bt" = (
-/obj/structure/stone_tile/surrounding_tile{
- dir = 1
- },
-/obj/structure/stone_tile/surrounding_tile,
-/obj/structure/stone_tile/surrounding_tile/cracked{
- dir = 4
- },
-/obj/structure/stone_tile/center,
-/turf/open/chasm/lavaland,
-/area/ruin/unpowered/xenonest)
-"bu" = (
-/obj/structure/stone_tile/block/cracked{
- dir = 1
- },
-/turf/open/chasm/lavaland,
-/area/ruin/unpowered/xenonest)
-"bv" = (
-/obj/structure/stone_tile/block{
- dir = 8
- },
-/obj/structure/stone_tile{
- dir = 1
- },
-/obj/structure/stone_tile/cracked,
-/turf/open/indestructible/boss,
-/area/ruin/unpowered/xenonest)
"bw" = (
/obj/structure/stone_tile/cracked{
dir = 4
@@ -492,19 +503,6 @@
/obj/structure/alien/weeds,
/turf/template_noop,
/area/ruin/unpowered/xenonest)
-"bE" = (
-/obj/structure/stone_tile/surrounding_tile/cracked{
- dir = 4
- },
-/obj/structure/stone_tile/surrounding_tile/cracked{
- dir = 8
- },
-/obj/structure/stone_tile/surrounding_tile/cracked{
- dir = 1
- },
-/obj/structure/stone_tile/center/cracked,
-/turf/open/chasm/lavaland,
-/area/ruin/unpowered/xenonest)
"dE" = (
/obj/structure/alien/weeds,
/obj/structure/bed/nest,
@@ -514,6 +512,12 @@
/obj/item/clothing/mask/facehugger/impregnated,
/turf/open/floor/plating/asteroid/basalt/lava_land_surface,
/area/ruin/unpowered/xenonest)
+"gA" = (
+/obj/structure/alien/weeds,
+/obj/structure/bed/nest,
+/obj/effect/mob_spawn/alien/corpse/humanoid/sentinel,
+/turf/open/floor/plating/asteroid/basalt/lava_land_surface,
+/area/ruin/unpowered/xenonest)
"iA" = (
/obj/structure/alien/weeds,
/obj/structure/bed/nest,
@@ -522,18 +526,24 @@
/obj/item/clothing/mask/facehugger/impregnated,
/turf/open/floor/plating/asteroid/basalt/lava_land_surface,
/area/ruin/unpowered/xenonest)
-"kp" = (
-/obj/structure/alien/weeds,
-/obj/item/reagent_containers/syringe/alien,
-/turf/open/floor/plating/asteroid/basalt/lava_land_surface,
-/area/ruin/unpowered/xenonest)
"lG" = (
/obj/structure/alien/weeds,
/obj/effect/mob_spawn/alien/corpse/humanoid/drone,
/turf/open/floor/plating/asteroid/basalt/lava_land_surface,
/area/ruin/unpowered/xenonest)
-"pE" = (
-/obj/effect/mob_spawn/alien/corpse/humanoid/queen,
+"lM" = (
+/obj/structure/stone_tile/block{
+ dir = 4
+ },
+/turf/open/chasm/lavaland,
+/area/ruin/unpowered/xenonest)
+"nj" = (
+/obj/effect/mob_spawn/alien/corpse/humanoid/praetorian,
+/turf/open/floor/plating/asteroid/basalt/lava_land_surface,
+/area/ruin/unpowered/xenonest)
+"tY" = (
+/obj/structure/alien/weeds/node,
+/obj/effect/mob_spawn/alien/corpse/humanoid/hunter,
/turf/open/floor/plating/asteroid/basalt/lava_land_surface,
/area/ruin/unpowered/xenonest)
"wA" = (
@@ -546,12 +556,23 @@
/mob/living/simple_animal/hostile/alien/sentinel,
/turf/open/floor/plating/asteroid/basalt/lava_land_surface,
/area/ruin/unpowered/xenonest)
+"zj" = (
+/obj/structure/stone_tile/block/cracked{
+ dir = 1
+ },
+/turf/open/chasm/lavaland,
+/area/ruin/unpowered/xenonest)
"Dd" = (
/obj/structure/alien/weeds,
/obj/effect/decal/cleanable/blood/gibs,
/obj/item/storage/backpack/duffelbag/sec/surgery,
/turf/open/floor/plating/asteroid/basalt/lava_land_surface,
/area/ruin/unpowered/xenonest)
+"Ha" = (
+/obj/structure/alien/weeds,
+/obj/item/reagent_containers/syringe/alien,
+/turf/open/floor/plating/asteroid/basalt/lava_land_surface,
+/area/ruin/unpowered/xenonest)
"JM" = (
/obj/structure/alien/weeds/node,
/obj/effect/mob_spawn/alien/corpse/humanoid/drone,
@@ -1075,7 +1096,7 @@ ac
ac
ac
bi
-aY
+ax
ac
ac
ac
@@ -1123,7 +1144,7 @@ an
an
ac
ac
-am
+gA
ac
ac
aJ
@@ -1278,10 +1299,10 @@ ac
Dd
ag
ak
-ag
+aP
af
ac
-lG
+ag
ac
ac
an
@@ -1357,7 +1378,7 @@ ab
ac
af
ag
-ar
+tY
lG
ag
ag
@@ -1511,7 +1532,7 @@ ac
ah
af
ah
-JM
+ar
Vi
ak
ag
@@ -1618,7 +1639,7 @@ ag
wA
ag
ag
-pE
+nj
ag
at
ag
@@ -1640,7 +1661,7 @@ ab
aZ
aZ
aZ
-bm
+ay
aZ
aZ
aZ
@@ -1718,7 +1739,7 @@ ah
ar
ah
ak
-kp
+Ha
at
ag
ag
@@ -1742,8 +1763,8 @@ ac
aZ
bc
bj
-bp
-bt
+lM
+aD
bx
aZ
ab
@@ -1794,7 +1815,7 @@ ba
bd
bk
bq
-bu
+zj
by
aZ
ab
@@ -1823,7 +1844,7 @@ ac
ag
ar
ag
-ag
+aO
ac
ab
ab
@@ -1845,7 +1866,7 @@ aZ
be
bo
bs
-bE
+aK
bz
aZ
ab
@@ -1896,7 +1917,7 @@ aZ
bf
bl
br
-bv
+aN
bA
aZ
ab
diff --git a/_maps/RandomRuins/LavaRuins/lavaland_surface_dead_ratvar.dmm b/_maps/RandomRuins/LavaRuins/lavaland_surface_dead_ratvar.dmm
index d8713fd40a..134a692e41 100644
--- a/_maps/RandomRuins/LavaRuins/lavaland_surface_dead_ratvar.dmm
+++ b/_maps/RandomRuins/LavaRuins/lavaland_surface_dead_ratvar.dmm
@@ -155,6 +155,12 @@
/obj/item/stack/tile/brass/fifty,
/turf/open/floor/plating/asteroid/basalt/lava_land_surface,
/area/lavaland/surface/outdoors/unexplored)
+"Y" = (
+/obj/item/tcg_card/special/ratvar,
+/turf/open/floor/clockwork{
+ initial_gas_mix = "o2=14;n2=23;TEMP=300"
+ },
+/area/lavaland/surface/outdoors/unexplored)
(1,1,1) = {"
a
@@ -484,7 +490,7 @@ h
h
h
h
-h
+Y
h
l
l
@@ -709,7 +715,7 @@ b
l
l
b
-h
+Y
h
t
b
diff --git a/_maps/RandomRuins/LavaRuins/lavaland_surface_hierophant.dmm b/_maps/RandomRuins/LavaRuins/lavaland_surface_hierophant.dmm
index cad120c3f2..c307c2caae 100644
--- a/_maps/RandomRuins/LavaRuins/lavaland_surface_hierophant.dmm
+++ b/_maps/RandomRuins/LavaRuins/lavaland_surface_hierophant.dmm
@@ -26,6 +26,10 @@
},
/turf/open/indestructible/hierophant/two,
/area/ruin/unpowered/hierophant)
+"s" = (
+/obj/item/tcg_card/special/hierophant,
+/turf/open/indestructible/hierophant,
+/area/ruin/unpowered/hierophant)
(1,1,1) = {"
a
@@ -494,7 +498,7 @@ b
b
b
c
-b
+s
a
a
b
diff --git a/_maps/RandomRuins/LavaRuins/lavaland_surface_ufo_crash.dmm b/_maps/RandomRuins/LavaRuins/lavaland_surface_ufo_crash.dmm
index f4c8c7ea0e..7ac6d32b80 100644
--- a/_maps/RandomRuins/LavaRuins/lavaland_surface_ufo_crash.dmm
+++ b/_maps/RandomRuins/LavaRuins/lavaland_surface_ufo_crash.dmm
@@ -11,6 +11,13 @@
"d" = (
/turf/closed/wall/mineral/abductor,
/area/ruin/unpowered)
+"h" = (
+/obj/structure/closet/abductor,
+/obj/item/tcg_card/special/abductor,
+/turf/open/floor/plating/abductor{
+ initial_gas_mix = "o2=14;n2=23;TEMP=300"
+ },
+/area/ruin/unpowered)
"j" = (
/obj/machinery/abductor/experiment{
team_number = 100
@@ -188,7 +195,7 @@ a
a
c
d
-q
+h
t
q
d
diff --git a/_maps/RandomRuins/SpaceRuins/abandonedzoo.dmm b/_maps/RandomRuins/SpaceRuins/abandonedzoo.dmm
index d83b4cf2c3..d4b0e65f2d 100644
--- a/_maps/RandomRuins/SpaceRuins/abandonedzoo.dmm
+++ b/_maps/RandomRuins/SpaceRuins/abandonedzoo.dmm
@@ -567,6 +567,7 @@
"bm" = (
/obj/structure/closet/wardrobe/science_white,
/obj/structure/disposalpipe/segment,
+/obj/item/tcg_card/special/space_carp,
/turf/open/floor/plasteel{
icon_state = "dark"
},
diff --git a/_maps/RandomRuins/SpaceRuins/caravanambush.dmm b/_maps/RandomRuins/SpaceRuins/caravanambush.dmm
index a1171400e6..bba36470dd 100644
--- a/_maps/RandomRuins/SpaceRuins/caravanambush.dmm
+++ b/_maps/RandomRuins/SpaceRuins/caravanambush.dmm
@@ -16,8 +16,8 @@
/area/template_noop)
"ae" = (
/obj/structure/fluff/broken_flooring{
- icon_state = "plating";
- dir = 4
+ dir = 4;
+ icon_state = "plating"
},
/turf/template_noop,
/area/template_noop)
@@ -66,8 +66,8 @@
"an" = (
/obj/structure/lattice,
/obj/structure/fluff/broken_flooring{
- icon_state = "plating";
- dir = 4
+ dir = 4;
+ icon_state = "plating"
},
/turf/template_noop,
/area/template_noop)
@@ -78,8 +78,8 @@
"ap" = (
/obj/structure/lattice,
/obj/structure/fluff/broken_flooring{
- icon_state = "pile";
- dir = 8
+ dir = 8;
+ icon_state = "pile"
},
/turf/template_noop,
/area/template_noop)
@@ -133,8 +133,8 @@
/area/template_noop)
"aE" = (
/obj/structure/fluff/broken_flooring{
- icon_state = "pile";
- dir = 4
+ dir = 4;
+ icon_state = "pile"
},
/turf/template_noop,
/area/template_noop)
@@ -1054,6 +1054,7 @@
/obj/effect/decal/cleanable/dirt,
/obj/structure/closet/crate/secure/weapon,
/obj/item/gun/ballistic/automatic/pistol/APS,
+/obj/item/tcg_card/special/spess_pirate,
/turf/open/floor/plasteel/airless/dark,
/area/shuttle/caravan/freighter2)
"js" = (
diff --git a/_maps/RandomRuins/SpaceRuins/cloning_facility.dmm b/_maps/RandomRuins/SpaceRuins/cloning_facility.dmm
index d440e2d15b..1f258fad77 100644
--- a/_maps/RandomRuins/SpaceRuins/cloning_facility.dmm
+++ b/_maps/RandomRuins/SpaceRuins/cloning_facility.dmm
@@ -294,7 +294,7 @@
/turf/template_noop,
/area/space/nearstation)
"N" = (
-/obj/item/book/random/triple,
+/obj/item/book/random,
/turf/open/floor/plasteel,
/area/ruin/space/has_grav/powered/ancient_shuttle)
"O" = (
diff --git a/_maps/RandomRuins/SpaceRuins/gondolaasteroid.dmm b/_maps/RandomRuins/SpaceRuins/gondolaasteroid.dmm
index e3b105d06e..ab5086e534 100644
--- a/_maps/RandomRuins/SpaceRuins/gondolaasteroid.dmm
+++ b/_maps/RandomRuins/SpaceRuins/gondolaasteroid.dmm
@@ -91,6 +91,10 @@
},
/turf/open/floor/plating/asteroid/airless,
/area/ruin/space/has_grav)
+"Y" = (
+/obj/item/tcg_card/special/gondola,
+/turf/open/floor/plating/asteroid/airless,
+/area/ruin/space/has_grav)
(1,1,1) = {"
a
@@ -339,7 +343,7 @@ b
c
c
c
-c
+Y
o
c
r
diff --git a/_maps/RandomRuins/SpaceRuins/mechtransport.dmm b/_maps/RandomRuins/SpaceRuins/mechtransport.dmm
index 6e35de07be..a9beb9540e 100644
--- a/_maps/RandomRuins/SpaceRuins/mechtransport.dmm
+++ b/_maps/RandomRuins/SpaceRuins/mechtransport.dmm
@@ -19,6 +19,7 @@
/area/ruin/space/has_grav/powered/mechtransport)
"g" = (
/obj/structure/closet/crate/secure/loot,
+/obj/item/tcg_card/special/phazon,
/turf/open/floor/mineral/titanium/blue,
/area/ruin/space/has_grav/powered/mechtransport)
"h" = (
diff --git a/_maps/RandomRuins/SpaceRuins/oldstation.dmm b/_maps/RandomRuins/SpaceRuins/oldstation.dmm
index 8528099d30..43aea1761a 100644
--- a/_maps/RandomRuins/SpaceRuins/oldstation.dmm
+++ b/_maps/RandomRuins/SpaceRuins/oldstation.dmm
@@ -1651,7 +1651,7 @@
/turf/open/floor/plasteel/white,
/area/ruin/space/has_grav/ancientstation/rnd)
"eD" = (
-/obj/machinery/mecha_part_fabricator/offstation,
+/obj/machinery/mecha_part_fabricator,
/obj/effect/decal/cleanable/dirt,
/turf/open/floor/plasteel/white,
/area/ruin/space/has_grav/ancientstation/rnd)
@@ -2010,12 +2010,12 @@
/turf/open/floor/plasteel,
/area/ruin/space/has_grav/ancientstation/deltacorridor)
"fu" = (
-/obj/machinery/rnd/production/protolathe/offstation,
+/obj/machinery/rnd/production/protolathe,
/obj/effect/decal/cleanable/dirt,
/turf/open/floor/plasteel/white,
/area/ruin/space/has_grav/ancientstation/rnd)
"fv" = (
-/obj/machinery/rnd/production/circuit_imprinter/offstation,
+/obj/machinery/rnd/production/circuit_imprinter,
/obj/effect/decal/cleanable/dirt,
/obj/item/reagent_containers/dropper,
/turf/open/floor/plasteel/white,
diff --git a/_maps/RandomRuins/SpaceRuins/skelter.dmm b/_maps/RandomRuins/SpaceRuins/skelter.dmm
index f6e56a6348..77837392e4 100644
--- a/_maps/RandomRuins/SpaceRuins/skelter.dmm
+++ b/_maps/RandomRuins/SpaceRuins/skelter.dmm
@@ -2729,9 +2729,7 @@
/turf/open/floor/carpet,
/area/ruin/space/has_grav/skelter/admin)
"gx" = (
-/obj/machinery/door/firedoor{
- pixel_x = 0
- },
+/obj/machinery/door/firedoor,
/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden,
/obj/structure/cable/yellow{
icon_state = "1-2"
@@ -2742,9 +2740,7 @@
/turf/open/floor/plasteel/showroomfloor,
/area/ruin/space/has_grav/skelter/shields)
"gy" = (
-/obj/machinery/door/firedoor{
- pixel_x = 0
- },
+/obj/machinery/door/firedoor,
/obj/machinery/atmospherics/pipe/simple/supply/hidden,
/obj/machinery/door/airlock/engineering{
name = "Shields"
diff --git a/_maps/RandomRuins/StationRuins/Lavaland/Mining_Station/Mining_Station_Public_01.dmm b/_maps/RandomRuins/StationRuins/Lavaland/Mining_Station/Mining_Station_Public_01.dmm
index 1aed2fdbb0..69cf7c0867 100644
--- a/_maps/RandomRuins/StationRuins/Lavaland/Mining_Station/Mining_Station_Public_01.dmm
+++ b/_maps/RandomRuins/StationRuins/Lavaland/Mining_Station/Mining_Station_Public_01.dmm
@@ -3170,7 +3170,9 @@
/turf/open/lava/smooth/lava_land_surface,
/area/lavaland/surface/outdoors)
"VY" = (
-/obj/structure/closet/emcloset,
+/obj/structure/closet/emcloset{
+ anchored = 1
+ },
/obj/effect/turf_decal/tile/purple{
dir = 1
},
diff --git a/_maps/RandomZLevels/away_mission/SnowCabin.dmm b/_maps/RandomZLevels/away_mission/SnowCabin.dmm
index fbb83899fd..dda382c4b7 100644
--- a/_maps/RandomZLevels/away_mission/SnowCabin.dmm
+++ b/_maps/RandomZLevels/away_mission/SnowCabin.dmm
@@ -881,7 +881,7 @@
/turf/open/floor/plating,
/area/awaymission/cabin)
"cT" = (
-/obj/vehicle/ridden/atv,
+/obj/vehicle/ridden/atv/snowmobile,
/turf/open/floor/plating{
icon_state = "platingdmg3"
},
@@ -893,7 +893,7 @@
/turf/open/floor/plating,
/area/awaymission/cabin)
"cV" = (
-/obj/vehicle/ridden/atv,
+/obj/vehicle/ridden/atv/snowmobile,
/turf/open/floor/plating,
/area/awaymission/cabin)
"cW" = (
diff --git a/_maps/RandomZLevels/away_mission/jungleresort.dmm b/_maps/RandomZLevels/away_mission/jungleresort.dmm
index d4698fec9a..9ff92b619a 100644
--- a/_maps/RandomZLevels/away_mission/jungleresort.dmm
+++ b/_maps/RandomZLevels/away_mission/jungleresort.dmm
@@ -14,7 +14,7 @@
"ai" = (
/obj/effect/turf_decal/sand/plating,
/obj/structure/closet/crate/secure/loot,
-/obj/item/clothing/head/collectable/paper,
+/obj/item/clothing/head/sombrero/shamebrero,
/turf/open/floor/plating,
/area/awaymission/jungleresort)
"ak" = (
@@ -51,6 +51,10 @@
/obj/machinery/jukebox,
/turf/open/floor/wood,
/area/awaymission/jungleresort)
+"az" = (
+/obj/structure/stone_tile/center,
+/turf/open/floor/plating/dirt/jungle,
+/area/awaymission/jungleresort)
"aA" = (
/obj/machinery/light,
/obj/structure/chair/wood{
@@ -199,7 +203,7 @@
/area/awaymission/jungleresort)
"cK" = (
/obj/structure/closet/crate,
-/obj/item/clothing/head/collectable/tophat,
+/obj/item/clothing/head/collectable/petehat/gang,
/turf/open/floor/plating/rust,
/area/awaymission/jungleresort)
"cT" = (
@@ -217,6 +221,11 @@
/obj/structure/flora/rock,
/turf/open/floor/plating/dirt/jungle,
/area/awaymission/jungleresort)
+"cY" = (
+/obj/structure/flora/grass/jungle/b,
+/mob/living/simple_animal/hostile/gorilla/jungle,
+/turf/open/floor/grass,
+/area/awaymission/jungleresort)
"dm" = (
/obj/structure/table/wood,
/obj/item/reagent_containers/food/drinks/beer,
@@ -274,7 +283,6 @@
dir = 9
},
/obj/structure/stone_tile/center/cracked,
-/obj/item/ammo_casing/shotgun/buckshot,
/turf/open/floor/plating/dirt/jungle,
/area/awaymission/jungleresort)
"eB" = (
@@ -407,8 +415,8 @@
/area/awaymission/jungleresort)
"gK" = (
/obj/structure/table/wood,
-/obj/item/gun/ballistic/automatic/l6_saw/toy/unrestricted/riot,
-/obj/item/ammo_box/magazine/toy/m762/riot,
+/obj/item/gun/ballistic/automatic/c20r/toy/unrestricted,
+/obj/item/ammo_box/magazine/toy/smgm45,
/turf/open/floor/wood,
/area/awaymission/jungleresort)
"gL" = (
@@ -580,6 +588,9 @@
dir = 1
},
/obj/structure/stone_tile/center/burnt,
+/obj/structure/stone_tile/surrounding_tile/cracked{
+ dir = 6
+ },
/turf/open/floor/plating/dirt/jungle,
/area/awaymission/jungleresort)
"iE" = (
@@ -660,6 +671,11 @@
},
/turf/open/floor/grass,
/area/awaymission/jungleresort)
+"jw" = (
+/obj/structure/stone_tile/surrounding/cracked,
+/obj/structure/stone_tile/center/burnt,
+/turf/open/floor/plating/dirt/jungle,
+/area/awaymission/jungleresort)
"jy" = (
/obj/structure/flora/grass/jungle/b,
/obj/effect/turf_decal/weather/dirt{
@@ -674,6 +690,16 @@
},
/turf/open/floor/grass,
/area/awaymission/jungleresort)
+"jD" = (
+/obj/structure/stone_tile/surrounding_tile/cracked,
+/obj/structure/stone_tile/surrounding_tile/cracked{
+ dir = 9
+ },
+/obj/structure/stone_tile/surrounding_tile/cracked{
+ dir = 6
+ },
+/turf/open/floor/plating/dirt/jungle,
+/area/awaymission/jungleresort)
"jF" = (
/obj/structure/flora/tree/jungle,
/obj/machinery/light,
@@ -798,6 +824,11 @@
},
/turf/open/floor/wood,
/area/awaymission/jungleresort)
+"lF" = (
+/obj/structure/flora/junglebush,
+/mob/living/simple_animal/hostile/gorilla/jungle,
+/turf/open/floor/grass,
+/area/awaymission/jungleresort)
"lJ" = (
/obj/structure/flora/junglebush/c,
/obj/machinery/light{
@@ -869,6 +900,8 @@
/obj/structure/cable{
icon_state = "2-4"
},
+/obj/item/clothing/glasses/meson/engine,
+/obj/item/storage/belt/utility,
/turf/open/floor/plating,
/area/awaymission/jungleresort)
"mE" = (
@@ -986,6 +1019,10 @@
/obj/item/toy/crayon/spraycan,
/turf/open/floor/plating/dirt/jungle,
/area/awaymission/jungleresort)
+"ou" = (
+/obj/item/clothing/glasses/meson,
+/turf/open/floor/plating/asteroid,
+/area/awaymission/jungleresort)
"ow" = (
/obj/structure/flora/tree/jungle,
/obj/effect/turf_decal/weather/dirt,
@@ -993,7 +1030,7 @@
/area/awaymission/jungleresort)
"oW" = (
/obj/effect/decal/remains/human,
-/obj/item/clothing/head/collectable/petehat/gang,
+/obj/item/clothing/head/collectable/tophat,
/turf/open/floor/plating,
/area/awaymission/jungleresort)
"oX" = (
@@ -1075,7 +1112,7 @@
"qi" = (
/obj/effect/turf_decal/sand/plating,
/obj/structure/closet/crate/secure/loot,
-/obj/item/clothing/head/collectable/beret,
+/obj/item/clothing/head/rice_hat/cursed,
/turf/open/floor/plating,
/area/awaymission/jungleresort)
"qr" = (
@@ -1255,6 +1292,15 @@
/obj/effect/turf_decal/sand/plating,
/turf/open/floor/plating,
/area/awaymission/jungleresort)
+"tm" = (
+/obj/structure/stone_tile/surrounding_tile/cracked{
+ dir = 9
+ },
+/obj/structure/stone_tile/surrounding_tile/cracked{
+ dir = 1
+ },
+/turf/open/floor/plating/dirt/jungle,
+/area/awaymission/jungleresort)
"tn" = (
/obj/structure/flora/rock/pile,
/obj/machinery/light{
@@ -1478,6 +1524,15 @@
/obj/effect/turf_decal/weather/dirt,
/turf/open/floor/grass,
/area/awaymission/jungleresort)
+"vz" = (
+/obj/structure/stone_tile/surrounding_tile/cracked{
+ dir = 6
+ },
+/obj/structure/stone_tile/surrounding/cracked{
+ dir = 1
+ },
+/turf/open/floor/plating/dirt/jungle,
+/area/awaymission/jungleresort)
"vK" = (
/obj/structure/flora/ausbushes/stalkybush,
/turf/open/floor/grass,
@@ -1497,6 +1552,10 @@
},
/turf/open/floor/wood,
/area/awaymission/jungleresort)
+"wd" = (
+/obj/item/storage/bag/ore,
+/turf/open/floor/plating/asteroid,
+/area/awaymission/jungleresort)
"wj" = (
/obj/machinery/light{
dir = 1
@@ -1535,7 +1594,7 @@
/area/awaymission/jungleresort)
"ww" = (
/obj/structure/closet/secure_closet/personal/cabinet,
-/obj/item/clothing/suit/hooded/wintercoat/captain,
+/obj/item/clothing/suit/hooded/wintercoat/captain/jungle,
/turf/open/floor/carpet/blue,
/area/awaymission/jungleresort)
"wF" = (
@@ -1655,6 +1714,10 @@
},
/turf/open/floor/grass,
/area/awaymission/jungleresort)
+"yw" = (
+/obj/item/pickaxe/mini,
+/turf/open/floor/plating/asteroid,
+/area/awaymission/jungleresort)
"yx" = (
/obj/structure/flora/ausbushes/fullgrass,
/obj/effect/turf_decal/weather/dirt{
@@ -1814,6 +1877,13 @@
/obj/structure/flora/ausbushes/sparsegrass,
/turf/open/floor/grass,
/area/awaymission/jungleresort)
+"Ad" = (
+/obj/structure/stone_tile/surrounding_tile/cracked{
+ dir = 1
+ },
+/obj/structure/stone_tile/surrounding_tile/cracked,
+/turf/open/floor/plating/dirt/jungle,
+/area/awaymission/jungleresort)
"Ag" = (
/obj/machinery/door/airlock/wood{
name = "Resort Office"
@@ -2016,6 +2086,7 @@
/obj/structure/window/reinforced{
dir = 4
},
+/obj/structure/window/reinforced,
/turf/open/floor/carpet,
/area/awaymission/jungleresort)
"CV" = (
@@ -2329,6 +2400,9 @@
/obj/structure/stone_tile/surrounding_tile/cracked{
dir = 9
},
+/obj/structure/stone_tile/surrounding/cracked{
+ dir = 1
+ },
/turf/open/floor/plating/dirt/jungle,
/area/awaymission/jungleresort)
"GN" = (
@@ -2368,6 +2442,10 @@
},
/turf/open/floor/grass,
/area/awaymission/jungleresort)
+"Hr" = (
+/obj/structure/stone_tile/surrounding/cracked,
+/turf/open/floor/plating/dirt/jungle,
+/area/awaymission/jungleresort)
"Hs" = (
/obj/structure/flora/ausbushes/stalkybush,
/obj/machinery/light,
@@ -2410,6 +2488,10 @@
},
/turf/open/floor/grass,
/area/awaymission/jungleresort)
+"HT" = (
+/mob/living/simple_animal/hostile/gorilla/jungle,
+/turf/open/floor/grass,
+/area/awaymission/jungleresort)
"Ih" = (
/turf/open/floor/plating,
/area/awaymission/jungleresort)
@@ -2897,6 +2979,15 @@
/obj/item/storage/book/bible,
/turf/open/floor/carpet,
/area/awaymission/jungleresort)
+"ON" = (
+/obj/effect/decal/cleanable/blood/splatter,
+/obj/effect/decal/remains/human,
+/obj/structure/stone_tile/burnt,
+/obj/structure/stone_tile/surrounding_tile/cracked{
+ dir = 1
+ },
+/turf/open/floor/plating/dirt/jungle,
+/area/awaymission/jungleresort)
"OU" = (
/obj/effect/turf_decal/weather/dirt,
/turf/open/floor/grass,
@@ -3030,7 +3121,7 @@
/obj/effect/decal/cleanable/blood/tracks{
dir = 8
},
-/obj/structure/stone_tile/surrounding/cracked,
+/obj/structure/stone_tile/center/burnt,
/turf/open/floor/plating/dirt/jungle,
/area/awaymission/jungleresort)
"QA" = (
@@ -3091,6 +3182,7 @@
/obj/item/stack/sheet/mineral/uranium,
/obj/item/stack/sheet/mineral/uranium,
/obj/item/clothing/glasses/meson/engine,
+/obj/item/storage/belt/utility,
/turf/open/floor/plating,
/area/awaymission/jungleresort)
"Rm" = (
@@ -3224,11 +3316,8 @@
/obj/effect/decal/remains/human,
/obj/effect/decal/cleanable/blood/old,
/obj/structure/stone_tile/surrounding/cracked,
-/obj/item/gun/ballistic/shotgun/sc_pump,
/obj/item/clothing/suit/armor/vest,
/obj/item/clothing/head/helmet,
-/obj/item/ammo_casing/shotgun/buckshot,
-/obj/item/ammo_casing/shotgun/buckshot,
/turf/open/floor/plating/dirt/jungle,
/area/awaymission/jungleresort)
"SV" = (
@@ -3246,6 +3335,7 @@
/obj/structure/stone_tile/surrounding_tile/cracked{
dir = 5
},
+/obj/structure/stone_tile/burnt,
/turf/open/floor/plating/dirt/jungle,
/area/awaymission/jungleresort)
"Ta" = (
@@ -3290,6 +3380,7 @@
/obj/structure/stone_tile/surrounding_tile/cracked{
dir = 9
},
+/obj/structure/stone_tile/center,
/turf/open/floor/plating/dirt/jungle,
/area/awaymission/jungleresort)
"TG" = (
@@ -3508,7 +3599,7 @@
/area/awaymission/jungleresort)
"WD" = (
/obj/effect/decal/remains/human,
-/obj/item/clothing/head/collectable/slime,
+/obj/item/clothing/head/collectable/beret,
/turf/open/floor/plating/rust,
/area/awaymission/jungleresort)
"WE" = (
@@ -3573,6 +3664,11 @@
},
/turf/open/floor/plating/dirt/jungle,
/area/awaymission/jungleresort)
+"Xu" = (
+/obj/structure/flora/junglebush/c,
+/mob/living/simple_animal/hostile/gorilla/jungle,
+/turf/open/floor/grass,
+/area/awaymission/jungleresort)
"Xz" = (
/turf/open/floor/carpet/red,
/area/awaymission/jungleresort)
@@ -3639,6 +3735,7 @@
"Ye" = (
/obj/structure/closet/secure_closet/personal/cabinet,
/obj/item/toy/figure/syndie,
+/obj/item/gun/ballistic/automatic/toy/pistol/unrestricted,
/turf/open/floor/carpet/red,
/area/awaymission/jungleresort)
"Yf" = (
@@ -3748,6 +3845,9 @@
dir = 8
},
/obj/structure/stone_tile/surrounding_tile/burnt,
+/obj/structure/stone_tile/surrounding_tile/cracked{
+ dir = 9
+ },
/turf/open/floor/plating/dirt/jungle,
/area/awaymission/jungleresort)
"ZH" = (
@@ -6362,8 +6462,8 @@ AR
AR
AR
AR
-AR
oZ
+jD
aL
fy
Dr
@@ -6513,8 +6613,8 @@ AR
AR
AR
AR
-AR
-AR
+oZ
+Gk
AC
GD
oZ
@@ -6668,8 +6768,8 @@ AR
oZ
iv
Qz
-AR
-AR
+tm
+oZ
AR
AR
AR
@@ -6819,6 +6919,7 @@ AR
oZ
wS
SZ
+Hr
oZ
AR
AR
@@ -6834,7 +6935,6 @@ AR
AR
AR
AR
-AR
rd
AF
YQ
@@ -6967,11 +7067,11 @@ AR
AR
AR
AR
-AR
-AR
-yJ
oZ
-AR
+aL
+yJ
+TD
+oZ
AR
AR
AR
@@ -7122,6 +7222,8 @@ AR
AR
oZ
Yp
+az
+ON
oZ
AR
AR
@@ -7137,8 +7239,6 @@ AR
AR
AR
AR
-AR
-AR
YQ
YM
cn
@@ -7166,7 +7266,7 @@ YQ
YQ
CN
YQ
-YQ
+HT
rd
YQ
YQ
@@ -7274,6 +7374,8 @@ AR
AR
oZ
TD
+Ad
+jw
oZ
AR
AR
@@ -7289,8 +7391,6 @@ AR
AR
AR
AR
-AR
-AR
YQ
aP
Wl
@@ -7427,7 +7527,7 @@ AR
oZ
aL
Xs
-AR
+oZ
AR
AR
AR
@@ -7579,7 +7679,7 @@ AR
AR
oZ
tH
-AR
+vz
AR
AR
AR
@@ -8261,7 +8361,7 @@ YQ
YQ
MW
CN
-AF
+lF
Wl
YQ
YQ
@@ -9006,7 +9106,7 @@ YQ
CN
YQ
YQ
-YQ
+HT
Wl
aP
YQ
@@ -9807,7 +9907,7 @@ YQ
YQ
YQ
YQ
-YQ
+HT
YQ
YQ
YQ
@@ -10950,7 +11050,7 @@ YQ
YQ
Fz
wH
-YQ
+HT
YQ
YQ
VU
@@ -11390,9 +11490,9 @@ AR
AR
AR
io
-uh
-pQ
-pQ
+VJ
+AV
+AV
QA
dF
io
@@ -11544,8 +11644,8 @@ AR
AR
AR
AR
-AR
-pQ
+io
+VJ
AV
QA
QA
@@ -14218,7 +14318,7 @@ YQ
cn
ZV
MW
-YQ
+HT
rd
aP
vK
@@ -15051,7 +15151,7 @@ Wl
YQ
YQ
YQ
-YQ
+HT
rd
YQ
YQ
@@ -18177,8 +18277,8 @@ YQ
YQ
YQ
AF
-AR
-AR
+yw
+ou
AR
AR
AR
@@ -18329,9 +18429,9 @@ YQ
YM
Wl
Pg
-AR
-AR
-AR
+wd
+Qp
+Qp
AR
AR
AR
@@ -20699,7 +20799,7 @@ YQ
YQ
YQ
MW
-rd
+cY
Wl
YQ
YQ
@@ -22732,7 +22832,7 @@ AF
ZV
YQ
YQ
-cn
+Xu
YQ
YQ
YQ
@@ -23322,7 +23422,7 @@ YQ
CN
VU
dY
-YQ
+HT
YQ
aP
YQ
@@ -23577,7 +23677,7 @@ YQ
YQ
YQ
vK
-YQ
+HT
kr
YQ
AF
diff --git a/_maps/_basemap.dm b/_maps/_basemap.dm
index 213211fc42..bf5b4f7d49 100644
--- a/_maps/_basemap.dm
+++ b/_maps/_basemap.dm
@@ -13,7 +13,7 @@
#include "map_files\BoxStation\BoxStation.dmm"
#include "map_files\LambdaStation\lambda.dmm"
- #ifdef TRAVISBUILDING
+ #ifdef CIBUILDING
#include "templates.dm"
#endif
#endif
diff --git a/_maps/map_files/BoxStation/BoxStation.dmm b/_maps/map_files/BoxStation/BoxStation.dmm
index b81f55be21..f7245ef535 100644
--- a/_maps/map_files/BoxStation/BoxStation.dmm
+++ b/_maps/map_files/BoxStation/BoxStation.dmm
@@ -12995,22 +12995,35 @@
/obj/machinery/atmospherics/pipe/simple/supply/hidden{
dir = 4
},
-/turf/open/floor/plasteel/dark,
+/obj/machinery/hydroponics/soil,
+/turf/open/floor/grass,
/area/chapel/main)
"aCP" = (
/obj/machinery/atmospherics/pipe/simple/supply/hidden{
dir = 10
},
-/turf/open/floor/plasteel/dark,
+/obj/structure/table/wood,
+/obj/item/reagent_containers/food/snacks/grown/poppy{
+ pixel_y = 5
+ },
+/obj/item/reagent_containers/food/snacks/grown/harebell{
+ pixel_y = 5
+ },
+/obj/machinery/light{
+ dir = 1
+ },
+/turf/open/floor/wood/wood_large,
/area/chapel/main)
"aCQ" = (
/obj/machinery/atmospherics/pipe/simple/supply/hidden{
dir = 4
},
-/obj/machinery/light/small{
- dir = 1
+/obj/machinery/hydroponics/soil,
+/obj/structure/window/reinforced{
+ dir = 4;
+ pixel_x = 3
},
-/turf/open/floor/plasteel/dark,
+/turf/open/floor/grass,
/area/chapel/main)
"aCR" = (
/turf/closed/wall,
@@ -13536,7 +13549,7 @@
/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden{
dir = 4
},
-/turf/open/floor/plasteel/dark,
+/turf/open/floor/wood/wood_large,
/area/chapel/main)
"aEi" = (
/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden{
@@ -13556,35 +13569,24 @@
/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden{
dir = 10
},
-/turf/open/floor/plasteel/dark,
+/turf/open/floor/wood/wood_large,
/area/chapel/main)
"aEl" = (
/obj/effect/landmark/event_spawn,
/turf/open/floor/plating,
/area/maintenance/starboard/fore)
"aEm" = (
-/obj/machinery/door/window{
- dir = 8;
- name = "Mass Driver";
- req_access_txt = "22"
+/obj/structure/bookcase,
+/obj/machinery/camera{
+ c_tag = "Chapel North"
},
-/obj/machinery/mass_driver{
- dir = 4;
- id = "chapelgun";
- name = "Holy Driver"
- },
-/obj/effect/turf_decal/stripes/line{
- dir = 8
- },
-/turf/open/floor/plating,
+/turf/open/floor/wood/wood_large,
/area/chapel/main)
"aEn" = (
-/obj/machinery/door/poddoor{
- id = "chapelgun";
- name = "Chapel Launcher Door"
+/obj/structure/sign/warning/vacuum/external{
+ pixel_y = 32
},
-/obj/structure/fans/tiny,
-/turf/open/floor/plating,
+/turf/open/floor/wood/wood_large,
/area/chapel/main)
"aEz" = (
/obj/machinery/power/apc{
@@ -13603,13 +13605,10 @@
/obj/structure/cable{
icon_state = "1-2"
},
-/obj/structure/disposalpipe/sorting/mail{
- dir = 2;
- sortType = 18
- },
/obj/machinery/atmospherics/pipe/manifold/scrubbers/hidden{
dir = 4
},
+/obj/structure/disposalpipe/segment,
/turf/open/floor/plating,
/area/maintenance/starboard/fore)
"aEB" = (
@@ -13978,16 +13977,10 @@
/turf/open/floor/plasteel/dark,
/area/chapel/main)
"aFA" = (
-/obj/machinery/atmospherics/pipe/simple/supply/hidden,
-/obj/machinery/computer/pod/old{
- density = 0;
- icon = 'icons/obj/airlock_machines.dmi';
- icon_state = "airlock_control_standby";
- id = "chapelgun";
- name = "Mass Driver Controller";
- pixel_x = 24
+/obj/machinery/atmospherics/pipe/manifold/supply/hidden{
+ dir = 8
},
-/turf/open/floor/plasteel/dark,
+/turf/open/floor/wood/wood_large,
/area/chapel/main)
"aFB" = (
/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden,
@@ -14617,7 +14610,7 @@
departmentType = 2;
pixel_y = 30
},
-/turf/open/floor/plasteel/grimy,
+/turf/open/floor/wood/wood_large,
/area/chapel/office)
"aGV" = (
/obj/structure/disposalpipe/segment{
@@ -14652,7 +14645,7 @@
/obj/machinery/airalarm{
pixel_y = 25
},
-/turf/open/floor/plasteel/grimy,
+/turf/open/floor/wood/wood_large,
/area/chapel/office)
"aGZ" = (
/obj/machinery/door/airlock/security{
@@ -14703,7 +14696,7 @@
pixel_y = 25
},
/obj/machinery/vending/wardrobe/chap_wardrobe,
-/turf/open/floor/plasteel/grimy,
+/turf/open/floor/wood/wood_large,
/area/chapel/office)
"aHg" = (
/obj/machinery/light_switch{
@@ -14712,7 +14705,7 @@
/obj/machinery/camera{
c_tag = "Chapel Office"
},
-/turf/open/floor/plasteel/grimy,
+/turf/open/floor/wood/wood_large,
/area/chapel/office)
"aHh" = (
/obj/structure/cable{
@@ -14724,9 +14717,9 @@
/turf/open/floor/plasteel,
/area/gateway)
"aHi" = (
-/obj/structure/closet/crate/coffin,
-/obj/structure/window/reinforced{
- dir = 8
+/obj/machinery/door/morgue{
+ name = "Confession Booth (Chaplain)";
+ req_access_txt = "22"
},
/turf/open/floor/plasteel/dark,
/area/chapel/office)
@@ -14743,42 +14736,47 @@
/turf/open/floor/plasteel,
/area/gateway)
"aHk" = (
-/obj/machinery/atmospherics/components/unary/vent_pump/on{
- dir = 4
- },
-/turf/open/floor/plasteel/dark,
+/obj/effect/decal/cleanable/cobweb/cobweb2,
+/obj/structure/table/wood,
+/obj/item/clothing/under/misc/burial,
+/obj/item/clothing/under/misc/burial,
+/obj/item/clothing/under/misc/burial,
+/obj/item/clothing/under/misc/burial,
+/obj/item/clothing/under/misc/burial,
+/obj/item/clothing/under/misc/burial,
+/obj/item/clothing/under/misc/burial,
+/turf/open/floor/wood/wood_large,
/area/chapel/main)
"aHl" = (
-/obj/structure/closet/crate/coffin,
-/obj/machinery/door/window/eastleft{
- name = "Coffin Storage";
- req_access_txt = "22"
+/obj/structure/chair/comfy/plywood,
+/obj/machinery/light/floor,
+/obj/item/radio/intercom{
+ broadcasting = 1;
+ frequency = 1480;
+ name = "Confessional Intercom";
+ pixel_x = 25
},
+/obj/effect/decal/cleanable/cobweb/cobweb2,
/turf/open/floor/plasteel/dark,
/area/chapel/office)
"aHm" = (
-/obj/machinery/atmospherics/pipe/manifold/supply/hidden{
- dir = 4
- },
-/turf/open/floor/plasteel/dark,
+/obj/structure/lattice,
+/turf/closed/wall,
/area/chapel/main)
"aHn" = (
-/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden,
-/obj/machinery/atmospherics/pipe/simple/supply/hidden{
- dir = 4
+/obj/machinery/mass_driver{
+ dir = 4;
+ id = "chapelgun";
+ name = "Holy Driver"
},
-/turf/open/floor/plasteel/dark,
-/area/chapel/main)
-"aHo" = (
-/obj/structure/table/glass,
-/obj/item/reagent_containers/food/snacks/grown/poppy,
-/obj/item/reagent_containers/food/snacks/grown/harebell,
-/turf/open/floor/plasteel/chapel{
- dir = 4
+/obj/machinery/door/window{
+ dir = 8;
+ name = "Mass Driver";
+ req_access_txt = "22"
+ },
+/obj/effect/turf_decal/stripes/line{
+ dir = 8
},
-/area/chapel/main)
-"aHq" = (
-/obj/effect/spawner/structure/window/reinforced,
/turf/open/floor/plating,
/area/chapel/main)
"aHu" = (
@@ -15278,9 +15276,14 @@
/turf/open/floor/plasteel/dark,
/area/chapel/office)
"aIz" = (
-/obj/machinery/disposal/bin,
-/obj/structure/disposalpipe/trunk,
-/turf/open/floor/plasteel/grimy,
+/obj/structure/disposalpipe/segment{
+ dir = 6
+ },
+/obj/structure/table/wood,
+/obj/item/storage/crayons{
+ pixel_y = 8
+ },
+/turf/open/floor/wood/wood_large,
/area/chapel/office)
"aIB" = (
/obj/structure/bodycontainer/crematorium{
@@ -15293,20 +15296,16 @@
/area/chapel/office)
"aIC" = (
/obj/effect/landmark/start/chaplain,
-/obj/structure/chair,
-/turf/open/floor/plasteel/grimy,
-/area/chapel/office)
-"aID" = (
-/obj/structure/closet/crate/coffin,
-/obj/structure/window/reinforced{
+/obj/structure/chair/comfy/plywood,
+/obj/structure/disposalpipe/segment{
dir = 4
},
+/turf/open/floor/carpet,
+/area/chapel/office)
+"aID" = (
+/obj/effect/spawner/structure/window/reinforced/tinted,
/turf/open/floor/plasteel/dark,
/area/chapel/office)
-"aIE" = (
-/obj/structure/table/glass,
-/turf/open/floor/plasteel/chapel,
-/area/chapel/main)
"aIH" = (
/obj/structure/table,
/obj/item/storage/box/lights/mixed,
@@ -15328,7 +15327,7 @@
/area/construction/mining/aux_base)
"aII" = (
/obj/machinery/atmospherics/components/unary/vent_scrubber/on,
-/turf/open/floor/plasteel/grimy,
+/turf/open/floor/wood/wood_large,
/area/chapel/office)
"aIJ" = (
/obj/machinery/atmospherics/pipe/simple/supply/hidden,
@@ -15783,15 +15782,14 @@
/area/hydroponics)
"aJM" = (
/obj/structure/table/wood,
-/obj/item/flashlight/lamp{
- pixel_y = 10
- },
/obj/structure/disposalpipe/segment,
-/obj/item/nullrod,
/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden{
dir = 4
},
-/turf/open/floor/plasteel/grimy,
+/obj/item/flashlight/lamp{
+ pixel_y = 15
+ },
+/turf/open/floor/wood/wood_large,
/area/chapel/office)
"aJO" = (
/obj/structure/table,
@@ -15825,39 +15823,44 @@
/area/library)
"aJT" = (
/obj/structure/table/wood,
-/obj/item/paper_bin{
- pixel_x = -2;
- pixel_y = 5
- },
-/obj/item/storage/crayons,
/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden{
dir = 4
},
-/turf/open/floor/plasteel/grimy,
+/obj/item/paper_bin{
+ pixel_y = 4
+ },
+/obj/item/pen/fountain{
+ pixel_y = 4
+ },
+/turf/open/floor/carpet,
/area/chapel/office)
"aJU" = (
/obj/structure/table/wood,
-/obj/item/pen,
-/obj/item/reagent_containers/food/drinks/bottle/holywater,
+/obj/item/reagent_containers/food/drinks/bottle/holywater{
+ pixel_x = 9;
+ pixel_y = 4
+ },
/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden{
dir = 4
},
-/turf/open/floor/plasteel/grimy,
+/obj/item/nullrod{
+ pixel_x = -15;
+ pixel_y = 3
+ },
+/turf/open/floor/carpet,
/area/chapel/office)
"aJV" = (
-/obj/structure/closet/crate/coffin,
-/obj/machinery/door/window/eastleft{
- dir = 8;
- name = "Coffin Storage";
- req_access_txt = "22"
+/obj/effect/landmark/event_spawn,
+/obj/effect/turf_decal/stripes/line{
+ dir = 4
},
-/turf/open/floor/plasteel/dark,
-/area/chapel/office)
+/turf/open/floor/plating,
+/area/chapel/main)
"aJW" = (
/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden{
dir = 9
},
-/turf/open/floor/plasteel/grimy,
+/turf/open/floor/wood/wood_large,
/area/chapel/office)
"aJX" = (
/obj/machinery/atmospherics/pipe/simple/supply/hidden,
@@ -15916,10 +15919,10 @@
/turf/open/floor/plasteel,
/area/gateway)
"aKe" = (
-/obj/structure/table/glass,
-/turf/open/floor/plasteel/chapel{
+/obj/structure/chair/wood/normal{
dir = 4
},
+/turf/open/floor/carpet,
/area/chapel/main)
"aKf" = (
/obj/structure/cable{
@@ -16248,7 +16251,7 @@
/obj/machinery/atmospherics/pipe/simple/supply/hidden{
dir = 4
},
-/turf/open/floor/plasteel/grimy,
+/turf/open/floor/wood/wood_large,
/area/chapel/office)
"aLb" = (
/obj/structure/disposalpipe/segment{
@@ -16267,7 +16270,7 @@
/obj/machinery/atmospherics/components/unary/vent_pump/on{
dir = 8
},
-/turf/open/floor/plasteel/grimy,
+/turf/open/floor/wood/wood_large,
/area/chapel/office)
"aLd" = (
/obj/structure/table,
@@ -16286,13 +16289,13 @@
/turf/open/floor/plasteel,
/area/hydroponics)
"aLe" = (
-/obj/structure/chair{
- dir = 1
- },
/obj/machinery/atmospherics/pipe/simple/supply/hidden{
dir = 4
},
-/turf/open/floor/plasteel/grimy,
+/obj/structure/chair/wood/normal{
+ dir = 1
+ },
+/turf/open/floor/wood/wood_large,
/area/chapel/office)
"aLf" = (
/obj/machinery/airalarm{
@@ -16816,13 +16819,14 @@
/obj/machinery/atmospherics/components/unary/vent_scrubber/on{
dir = 4
},
+/obj/structure/chair/wood/wings{
+ dir = 8
+ },
/turf/open/floor/plasteel/dark,
/area/chapel/main)
"aMM" = (
-/obj/machinery/camera{
- c_tag = "Chapel North"
- },
-/turf/open/floor/plasteel/dark,
+/obj/machinery/atmospherics/pipe/simple/supply/hidden,
+/turf/open/floor/wood/wood_large,
/area/chapel/main)
"aMN" = (
/obj/machinery/chem_master/condimaster,
@@ -16887,7 +16891,7 @@
/turf/open/floor/plasteel,
/area/hallway/primary/port)
"aMX" = (
-/turf/open/floor/plasteel/grimy,
+/turf/open/floor/wood/wood_large,
/area/chapel/office)
"aMY" = (
/obj/machinery/atmospherics/pipe/manifold4w/scrubbers/hidden,
@@ -17177,28 +17181,24 @@
/turf/open/floor/wood,
/area/library)
"aNW" = (
-/obj/machinery/door/airlock/public/glass{
- name = "Chapel Office";
+/turf/open/floor/wood/wood_large,
+/area/chapel/main)
+"aNX" = (
+/obj/structure/closet/crate/coffin,
+/obj/machinery/door/window/eastright{
+ dir = 8;
+ name = "Coffin Storage";
req_access_txt = "22"
},
/turf/open/floor/plasteel/dark,
-/area/chapel/office)
-"aNX" = (
-/obj/item/radio/intercom{
- broadcasting = 1;
- frequency = 1480;
- name = "Confessional Intercom";
- pixel_x = 25
- },
-/obj/structure/chair,
-/turf/open/floor/plasteel/dark,
/area/chapel/main)
"aNY" = (
-/obj/machinery/door/morgue{
- name = "Confession Booth (Chaplain)";
- req_access_txt = "22"
+/obj/machinery/door/poddoor{
+ id = "chapelgun";
+ name = "Chapel Launcher Door"
},
-/turf/open/floor/plasteel/dark,
+/obj/structure/fans/tiny,
+/turf/open/floor/plating,
/area/chapel/main)
"aNZ" = (
/obj/structure/chair,
@@ -17679,7 +17679,12 @@
},
/area/chapel/main)
"aPo" = (
-/obj/effect/spawner/structure/window/reinforced/tinted,
+/obj/structure/closet/crate/coffin,
+/obj/machinery/door/window/eastleft{
+ dir = 8;
+ name = "Coffin Storage";
+ req_access_txt = "22"
+ },
/turf/open/floor/plasteel/dark,
/area/chapel/main)
"aPp" = (
@@ -18085,7 +18090,11 @@
/area/chapel/main)
"aQw" = (
/obj/structure/table/wood,
-/turf/open/floor/plasteel/dark,
+/obj/item/trash/candle{
+ pixel_x = -5;
+ pixel_y = 2
+ },
+/turf/open/floor/carpet,
/area/chapel/main)
"aQx" = (
/obj/machinery/atmospherics/pipe/simple/supply/hidden,
@@ -18098,22 +18107,17 @@
},
/area/chapel/main)
"aQz" = (
-/obj/item/radio/intercom{
- broadcasting = 1;
- frequency = 1480;
- name = "Confessional Intercom";
- pixel_x = 25
- },
-/obj/structure/chair{
- dir = 1
+/obj/structure/closet/crate/coffin,
+/obj/structure/window/reinforced{
+ dir = 8
},
/turf/open/floor/plasteel/dark,
/area/chapel/main)
"aQA" = (
-/obj/machinery/door/morgue{
- name = "Confession Booth"
+/obj/machinery/atmospherics/components/unary/vent_pump/on{
+ dir = 8
},
-/turf/open/floor/plasteel/dark,
+/turf/open/floor/wood/wood_large,
/area/chapel/main)
"aQB" = (
/obj/effect/turf_decal/tile/red{
@@ -19111,24 +19115,32 @@
/turf/open/floor/plasteel/dark,
/area/chapel/main)
"aTf" = (
-/obj/structure/chair/stool,
+/obj/structure/chair/pew/right{
+ dir = 1
+ },
/turf/open/floor/plasteel/chapel,
/area/chapel/main)
"aTg" = (
-/obj/structure/chair/stool,
+/obj/structure/chair/pew/left{
+ dir = 1
+ },
/turf/open/floor/plasteel/chapel{
dir = 8
},
/area/chapel/main)
"aTh" = (
-/obj/structure/chair/stool,
/obj/machinery/atmospherics/pipe/simple/supply/hidden,
+/obj/structure/chair/pew/right{
+ dir = 1
+ },
/turf/open/floor/plasteel/chapel,
/area/chapel/main)
"aTi" = (
-/obj/structure/chair/stool,
/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden,
/obj/effect/landmark/start/assistant,
+/obj/structure/chair/pew/left{
+ dir = 1
+ },
/turf/open/floor/plasteel/chapel{
dir = 8
},
@@ -19659,28 +19671,36 @@
/turf/open/floor/plasteel/dark,
/area/chapel/main)
"aUH" = (
-/obj/structure/chair/stool,
/obj/effect/landmark/start/assistant,
+/obj/structure/chair/pew/right{
+ dir = 1
+ },
/turf/open/floor/plasteel/chapel{
dir = 4
},
/area/chapel/main)
"aUI" = (
-/obj/structure/chair/stool,
+/obj/structure/chair/pew/left{
+ dir = 1
+ },
/turf/open/floor/plasteel/chapel{
dir = 1
},
/area/chapel/main)
"aUJ" = (
-/obj/structure/chair/stool,
/obj/machinery/atmospherics/pipe/simple/supply/hidden,
+/obj/structure/chair/pew/right{
+ dir = 1
+ },
/turf/open/floor/plasteel/chapel{
dir = 4
},
/area/chapel/main)
"aUK" = (
-/obj/structure/chair/stool,
/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden,
+/obj/structure/chair/pew/left{
+ dir = 1
+ },
/turf/open/floor/plasteel/chapel{
dir = 1
},
@@ -20207,8 +20227,10 @@
/turf/open/floor/wood,
/area/library)
"aVU" = (
-/obj/structure/chair/stool,
/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden,
+/obj/structure/chair/pew/left{
+ dir = 1
+ },
/turf/open/floor/plasteel/chapel{
dir = 8
},
@@ -21999,13 +22021,6 @@
},
/turf/open/floor/wood,
/area/crew_quarters/heads/captain)
-"bag" = (
-/obj/machinery/airalarm{
- dir = 1;
- pixel_y = -22
- },
-/turf/open/floor/wood,
-/area/crew_quarters/bar)
"bah" = (
/obj/structure/extinguisher_cabinet{
pixel_y = -30
@@ -23874,14 +23889,15 @@
/turf/closed/wall,
/area/maintenance/disposal)
"bfb" = (
-/obj/structure/sign/warning/vacuum/external{
- pixel_y = 32
+/obj/machinery/computer/pod/old{
+ density = 0;
+ icon = 'icons/obj/airlock_machines.dmi';
+ icon_state = "airlock_control_standby";
+ id = "chapelgun";
+ name = "Mass Driver Controller";
+ pixel_x = 24
},
-/obj/effect/landmark/event_spawn,
-/obj/effect/turf_decal/stripes/line{
- dir = 4
- },
-/turf/open/floor/plating,
+/turf/open/floor/wood/wood_large,
/area/chapel/main)
"bfc" = (
/obj/machinery/power/apc{
@@ -34004,9 +34020,6 @@
/area/medical/surgery)
"bCJ" = (
/obj/machinery/atmospherics/pipe/manifold/supply/hidden,
-/obj/machinery/light_switch{
- pixel_y = 26
- },
/turf/open/floor/plasteel/white,
/area/medical/surgery)
"bCK" = (
@@ -46267,7 +46280,7 @@
"chY" = (
/obj/machinery/shieldgen,
/turf/open/floor/plating,
-/area/engine/storage)
+/area/engine/engineering)
"cia" = (
/obj/effect/turf_decal/bot{
dir = 1
@@ -46617,7 +46630,7 @@
"ciW" = (
/obj/effect/landmark/blobstart,
/turf/open/floor/plating,
-/area/engine/storage)
+/area/engine/engineering)
"ciX" = (
/obj/structure/closet/crate,
/obj/item/stack/sheet/metal/fifty,
@@ -46632,14 +46645,14 @@
/obj/item/lightreplacer,
/obj/item/lightreplacer,
/turf/open/floor/plating,
-/area/engine/storage)
+/area/engine/engineering)
"ciY" = (
/obj/machinery/door/poddoor{
id = "Secure Storage";
name = "secure storage"
},
/turf/open/floor/plating,
-/area/engine/storage)
+/area/engine/engineering)
"ciZ" = (
/turf/open/floor/plating,
/area/engine/engineering)
@@ -46947,7 +46960,7 @@
dir = 4
},
/turf/open/floor/plating,
-/area/engine/storage)
+/area/engine/engineering)
"cjN" = (
/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden{
dir = 4
@@ -47234,11 +47247,11 @@
"ckB" = (
/obj/machinery/field/generator,
/turf/open/floor/plating,
-/area/engine/storage)
+/area/engine/engineering)
"ckC" = (
/obj/machinery/power/emitter,
/turf/open/floor/plating,
-/area/engine/storage)
+/area/engine/engineering)
"ckD" = (
/obj/effect/turf_decal/bot{
dir = 1
@@ -49323,9 +49336,12 @@
/turf/open/floor/plating,
/area/ai_monitored/turret_protected/aisat_interior)
"csT" = (
-/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden,
-/obj/effect/landmark/xmastree,
-/turf/open/floor/plasteel/dark,
+/obj/structure/table/glass,
+/obj/effect/decal/cleanable/cobweb/cobweb2,
+/obj/item/storage/book/bible{
+ pixel_y = -1
+ },
+/turf/open/floor/wood/wood_large,
/area/chapel/main)
"csU" = (
/obj/structure/transit_tube/station/reverse,
@@ -51957,14 +51973,11 @@
/turf/open/floor/plating,
/area/maintenance/starboard/aft)
"cBZ" = (
-/obj/structure/table/wood,
-/obj/item/clothing/under/misc/burial,
-/obj/item/clothing/under/misc/burial,
-/obj/item/clothing/under/misc/burial,
-/obj/item/clothing/under/misc/burial,
-/obj/item/clothing/under/misc/burial,
-/obj/item/clothing/under/misc/burial,
-/turf/open/floor/plasteel/grimy,
+/obj/structure/disposalpipe/trunk{
+ dir = 8
+ },
+/obj/machinery/disposal/bin,
+/turf/open/floor/carpet,
/area/chapel/office)
"cCb" = (
/obj/structure/table,
@@ -53407,10 +53420,6 @@
dir = 4
},
/obj/machinery/suit_storage_unit/atmos,
-/obj/machinery/airalarm{
- dir = 1;
- pixel_y = -22
- },
/obj/effect/turf_decal/stripes/line{
dir = 9
},
@@ -53432,6 +53441,10 @@
/obj/machinery/atmospherics/pipe/simple/supply/hidden,
/turf/open/floor/plasteel/dark,
/area/hallway/primary/central)
+"dmX" = (
+/obj/effect/spawner/structure/window/reinforced,
+/turf/open/floor/plating,
+/area/chapel/main)
"dnW" = (
/obj/effect/turf_decal/trimline/blue/filled/line{
dir = 8
@@ -53462,6 +53475,14 @@
icon_state = "carpetsymbol"
},
/area/crew_quarters/theatre)
+"dsJ" = (
+/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden,
+/obj/structure/table/glass,
+/obj/item/toy/figure/chaplain{
+ pixel_y = -9
+ },
+/turf/open/floor/wood/wood_large,
+/area/chapel/main)
"dtx" = (
/obj/structure/cable{
icon_state = "1-2"
@@ -54381,8 +54402,9 @@
/turf/open/floor/plasteel,
/area/security/range)
"fsQ" = (
-/turf/open/floor/plating,
-/area/engine/storage)
+/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden,
+/turf/open/floor/wood/wood_large,
+/area/chapel/main)
"fty" = (
/obj/structure/lattice,
/obj/machinery/atmospherics/pipe/simple/orange/visible,
@@ -56336,6 +56358,17 @@
/obj/machinery/atmospherics/pipe/simple/dark/visible,
/turf/open/space/basic,
/area/space/nearstation)
+"kdF" = (
+/obj/structure/window/reinforced{
+ dir = 1
+ },
+/obj/structure/closet/crate/coffin,
+/obj/structure/window/reinforced{
+ dir = 8
+ },
+/obj/effect/decal/cleanable/cobweb/cobweb2,
+/turf/open/floor/plasteel/dark,
+/area/chapel/main)
"kdO" = (
/obj/machinery/pool/controller,
/turf/open/floor/plasteel/yellowsiding,
@@ -56734,6 +56767,10 @@
},
/turf/open/floor/plating,
/area/maintenance/port/aft)
+"kOL" = (
+/obj/structure/table/glass,
+/turf/open/floor/wood/wood_large,
+/area/chapel/main)
"kPd" = (
/obj/machinery/atmospherics/components/unary/vent_pump/on,
/obj/structure/cable{
@@ -56947,6 +56984,17 @@
icon_state = "wood-broken6"
},
/area/maintenance/bar)
+"lqO" = (
+/obj/structure/cable{
+ icon_state = "1-2"
+ },
+/obj/machinery/atmospherics/pipe/simple/supply/hidden,
+/obj/structure/disposalpipe/sorting/mail/flip{
+ dir = 1;
+ sortType = 18
+ },
+/turf/open/floor/plating,
+/area/maintenance/fore)
"lre" = (
/obj/machinery/atmospherics/pipe/simple/supply/hidden,
/obj/structure/table/wood/poker,
@@ -57068,12 +57116,27 @@
},
/turf/closed/wall,
/area/maintenance/disposal/incinerator)
+"lLf" = (
+/obj/structure/disposalpipe/segment{
+ dir = 4
+ },
+/turf/closed/wall/r_wall,
+/area/maintenance/fore)
"lMg" = (
/obj/effect/turf_decal/stripes/line{
dir = 9
},
/turf/open/floor/plasteel,
/area/science/circuit)
+"lNB" = (
+/obj/machinery/atmospherics/pipe/simple/supply/hidden{
+ dir = 4
+ },
+/obj/effect/landmark/xmastree{
+ pixel_x = 14
+ },
+/turf/open/floor/carpet,
+/area/chapel/main)
"lNH" = (
/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden,
/obj/structure/cable{
@@ -57332,6 +57395,10 @@
/obj/effect/spawner/lootdrop/keg,
/turf/open/floor/wood,
/area/maintenance/bar)
+"mtU" = (
+/obj/structure/sign/departments/holy,
+/turf/closed/wall,
+/area/chapel/main)
"mug" = (
/obj/machinery/atmospherics/components/unary/vent_scrubber/on{
dir = 8
@@ -57366,6 +57433,12 @@
/obj/machinery/atmospherics/pipe/simple/supply/hidden,
/turf/open/floor/wood,
/area/crew_quarters/theatre)
+"mzv" = (
+/obj/machinery/door/morgue{
+ name = "Confession Booth"
+ },
+/turf/open/floor/wood/wood_large,
+/area/chapel/main)
"mzB" = (
/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden,
/obj/machinery/door/window,
@@ -57575,6 +57648,14 @@
},
/turf/open/floor/plasteel,
/area/crew_quarters/locker)
+"mZx" = (
+/obj/structure/table/glass,
+/obj/item/storage/box/matches{
+ pixel_x = 4;
+ pixel_y = -8
+ },
+/turf/open/floor/wood/wood_large,
+/area/chapel/main)
"naI" = (
/turf/open/space,
/area/space/station_ruins)
@@ -58588,8 +58669,11 @@
/obj/structure/cable{
icon_state = "1-2"
},
-/obj/machinery/door/airlock/vault,
/obj/effect/mapping_helpers/airlock/locked,
+/obj/machinery/door/airlock/vault{
+ name = "Vault Door";
+ req_access_txt = "53"
+ },
/turf/open/floor/plasteel/dark,
/area/ai_monitored/nuke_storage)
"puh" = (
@@ -58755,6 +58839,14 @@
},
/turf/open/floor/plating,
/area/maintenance/disposal)
+"pMQ" = (
+/obj/structure/window/reinforced,
+/obj/machinery/disposal/bin,
+/obj/structure/disposalpipe/trunk{
+ dir = 4
+ },
+/turf/open/floor/wood,
+/area/crew_quarters/theatre)
"pPi" = (
/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden{
dir = 4
@@ -58844,6 +58936,14 @@
icon_state = "wood-broken2"
},
/area/maintenance/port/fore)
+"pXG" = (
+/obj/structure/table/wood,
+/obj/item/candle{
+ pixel_x = 5;
+ pixel_y = 2
+ },
+/turf/open/floor/carpet,
+/area/chapel/main)
"pYQ" = (
/obj/structure/reagent_dispensers/watertank,
/obj/item/reagent_containers/glass/bucket,
@@ -58938,6 +59038,13 @@
},
/turf/open/floor/plating,
/area/maintenance/port/fore)
+"qkn" = (
+/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden,
+/obj/machinery/light{
+ dir = 8
+ },
+/turf/open/floor/wood/wood_large,
+/area/chapel/main)
"qkC" = (
/obj/structure/cable{
icon_state = "4-8"
@@ -59027,6 +59134,11 @@
/obj/structure/lattice,
/turf/closed/wall/r_wall,
/area/crew_quarters/heads/captain)
+"qCR" = (
+/obj/structure/musician/piano,
+/obj/effect/decal/cleanable/cobweb,
+/turf/open/floor/plasteel/dark,
+/area/chapel/main)
"qEB" = (
/obj/machinery/hydroponics/soil{
pixel_y = 8
@@ -59160,6 +59272,13 @@
},
/turf/open/floor/plasteel/white,
/area/medical/medbay/lobby)
+"qUh" = (
+/obj/machinery/door/airlock/public/glass{
+ name = "Chapel Office";
+ req_access_txt = "22"
+ },
+/turf/open/floor/plasteel/dark,
+/area/chapel/office)
"qVP" = (
/obj/effect/spawner/structure/window,
/obj/machinery/atmospherics/pipe/simple/supply/hidden{
@@ -59442,6 +59561,14 @@
},
/turf/open/floor/plasteel,
/area/security/prison)
+"rxF" = (
+/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden,
+/obj/structure/table/glass,
+/obj/item/storage/book/bible{
+ pixel_y = 17
+ },
+/turf/open/floor/wood/wood_large,
+/area/chapel/main)
"ryr" = (
/obj/effect/turf_decal/tile/blue,
/obj/effect/turf_decal/tile/blue{
@@ -60469,6 +60596,12 @@
},
/turf/open/floor/wood,
/area/maintenance/bar)
+"tSm" = (
+/obj/item/kirbyplants{
+ icon_state = "plant-18"
+ },
+/turf/open/floor/plasteel/dark,
+/area/chapel/main)
"tSo" = (
/obj/structure/lattice,
/obj/machinery/atmospherics/pipe/simple/dark/visible{
@@ -60752,11 +60885,13 @@
/obj/structure/cable{
icon_state = "2-8"
},
-/obj/structure/disposalpipe/junction,
/obj/effect/turf_decal/tile/neutral{
dir = 8
},
/obj/effect/turf_decal/tile/neutral,
+/obj/structure/disposalpipe/sorting/mail{
+ sortType = 26
+ },
/turf/open/floor/plasteel,
/area/crew_quarters/dorms)
"uys" = (
@@ -60943,7 +61078,6 @@
/area/science/circuit)
"uOJ" = (
/obj/effect/mapping_helpers/airlock/locked,
-/obj/machinery/door/airlock/vault,
/obj/structure/cable{
icon_state = "1-2"
},
@@ -60957,6 +61091,10 @@
/obj/effect/turf_decal/tile/neutral{
dir = 8
},
+/obj/machinery/door/airlock/vault{
+ name = "Vault Door";
+ req_access_txt = "53"
+ },
/turf/open/floor/plasteel/dark,
/area/ai_monitored/nuke_storage)
"uQR" = (
@@ -61202,6 +61340,19 @@
/obj/item/clothing/under/misc/pj/blue,
/turf/open/floor/plasteel,
/area/crew_quarters/fitness)
+"vqE" = (
+/obj/structure/chair/wood/normal{
+ dir = 1
+ },
+/obj/machinery/light/floor,
+/obj/item/radio/intercom{
+ broadcasting = 1;
+ frequency = 1480;
+ name = "Confessional Intercom";
+ pixel_x = -25
+ },
+/turf/open/floor/plasteel/dark,
+/area/chapel/main)
"vqP" = (
/obj/structure/bed/dogbed{
desc = "A comfy-looking pet bed. You can even strap your pet in, in case the gravity turns off.";
@@ -62236,8 +62387,17 @@
/turf/open/floor/carpet,
/area/library)
"xES" = (
-/turf/closed/wall/r_wall,
-/area/engine/storage)
+/obj/structure/table/glass,
+/obj/item/storage/fancy/candle_box{
+ pixel_x = 5;
+ pixel_y = 4
+ },
+/obj/item/storage/fancy/candle_box{
+ pixel_x = 1;
+ pixel_y = 4
+ },
+/turf/open/floor/wood/wood_large,
+/area/chapel/main)
"xFM" = (
/obj/item/clothing/gloves/color/rainbow,
/obj/item/clothing/head/soft/rainbow,
@@ -62327,6 +62487,12 @@
/obj/item/instrument/trombone,
/turf/open/floor/wood,
/area/crew_quarters/theatre)
+"xRa" = (
+/obj/item/kirbyplants{
+ icon_state = "plant-20"
+ },
+/turf/open/floor/plasteel/dark,
+/area/chapel/main)
"xSW" = (
/obj/effect/turf_decal/tile/red{
dir = 1
@@ -84482,7 +84648,7 @@ ayD
nez
ngV
xPY
-aOH
+pMQ
hcb
hcb
syJ
@@ -84739,7 +84905,7 @@ ayE
ayE
ayE
ayE
-ayE
+lLf
ayE
ayE
ayE
@@ -84996,7 +85162,7 @@ ayH
ayH
ayH
ayH
-ayH
+lqO
ayH
aFV
ayH
@@ -87889,14 +88055,14 @@ cdc
cdZ
bVI
cay
-xES
-xES
-xES
-xES
-xES
-xES
-xES
-xES
+ccw
+ccw
+ccw
+ccw
+ccw
+ccw
+ccw
+ccw
cfL
coH
cBO
@@ -88146,14 +88312,14 @@ bWB
cec
bVI
kNv
-xES
+ccw
chY
ciX
cjM
ckB
ckB
ckB
-xES
+ccw
cnY
coH
cgR
@@ -88403,14 +88569,14 @@ cde
ceb
bVI
cay
-xES
+ccw
chY
-fsQ
+ciZ
ciW
ckB
ckB
ckC
-xES
+ccw
cnX
coH
cps
@@ -88660,14 +88826,14 @@ cdf
ced
bVI
cay
-xES
-fsQ
-fsQ
-fsQ
+ccw
+ciZ
+ciZ
+ciZ
ckC
ckC
ckC
-xES
+ccw
coa
coJ
clJ
@@ -88917,14 +89083,14 @@ bWB
bWB
bVI
cay
-xES
-xES
+ccw
+ccw
ciY
ciY
-xES
-xES
-xES
-xES
+ccw
+ccw
+ccw
+ccw
cnZ
coH
cgI
@@ -99147,7 +99313,7 @@ aSZ
aQc
qaY
acN
-bag
+aKR
aJC
aYV
aYV
@@ -108390,7 +108556,7 @@ cBZ
aJT
aLc
aFw
-aFz
+tSm
aFz
aFz
aRR
@@ -108400,7 +108566,7 @@ aFz
aRS
aXW
baz
-aCR
+mtU
bcx
aXq
aYV
@@ -108646,7 +108812,7 @@ aGY
aII
aJW
aMX
-aNW
+qUh
aFz
aPl
aQv
@@ -108656,7 +108822,7 @@ aUI
aTg
aRS
aZf
-aFz
+xRa
bbF
aYV
aXq
@@ -108900,8 +109066,8 @@ aCM
aEg
aFw
aHi
-aHi
-aJV
+aFw
+aFw
aFw
aFw
aFz
@@ -109158,18 +109324,18 @@ aEi
aFw
aHl
aID
-aID
-aFw
-aMM
-aFz
+vqE
+aCR
+qCR
aFz
+aRS
aQw
aRS
aRS
aRS
aRS
aRS
-aZf
+lNB
aRS
bbF
aYV
@@ -109412,15 +109578,15 @@ aAz
asB
aCO
aEh
-aFz
-aHk
-aFz
-aFz
-aTe
+aCR
+aCR
+aCR
+mzv
+aCR
aML
aFz
-aFz
-aQw
+aRS
+pXG
cdl
aRS
aRS
@@ -109669,11 +109835,11 @@ aAA
asB
aCQ
aEk
-aFB
-aHn
-aFB
-csT
-aFB
+fsQ
+dsJ
+rxF
+fsQ
+qkn
aLr
aFB
aPn
@@ -109925,12 +110091,12 @@ awO
awO
asB
aCP
-aEj
+aMM
aFA
-aHm
-aEj
-aEj
-aEj
+aMM
+aMM
+aMM
+aMM
aEj
aEj
aPm
@@ -109941,7 +110107,7 @@ aUJ
aTh
aXz
aZg
-aFz
+xRa
bbF
aYV
bdv
@@ -110181,18 +110347,18 @@ ayj
azx
aAB
asB
-aCR
aEm
-aCR
-aPl
-aQv
-aPl
-aQv
-aCR
-aNY
-aCR
+aNW
aQA
-aCR
+aNW
+aNW
+aNW
+aNW
+aFz
+aFz
+aFz
+aFz
+aFz
aTj
aFz
aVV
@@ -110438,14 +110604,14 @@ asB
asB
asB
asB
-aCR
+aHk
+aNW
bfb
-aCR
-aHo
-aIE
aKe
-aIE
-aCR
+aKe
+aKe
+aNW
+kdF
aNX
aPo
aQz
@@ -110694,14 +110860,14 @@ atS
aaf
aaf
aaf
-atS
+asB
aCR
aEn
aCR
-aHq
-aHq
-aHq
-aHq
+aKe
+aKe
+aKe
+aNW
aCR
aCR
aCR
@@ -110951,14 +111117,14 @@ atS
aoV
aoV
aaf
-atS
-aaf
-aaa
-aaf
-aaa
-aaa
-aaa
-aaa
+gXs
+aHm
+aHn
+dmX
+aNW
+aNW
+aNW
+aNW
aMZ
aNZ
aPp
@@ -111208,14 +111374,14 @@ aaH
aoV
aoV
aaf
-atS
-aaf
-aaa
-aaf
-aaa
-aaa
-aaa
-aaf
+gXs
+aHm
+aJV
+dmX
+csT
+kOL
+mZx
+xES
aMZ
aOb
aPr
@@ -111465,14 +111631,14 @@ aaH
aoV
aoV
aoV
-atS
-aaf
-aaa
-aaf
-aaa
-aaa
-aaa
-aaf
+gXs
+aHm
+aNY
+aCR
+dmX
+dmX
+dmX
+dmX
aMZ
aOa
aVX
@@ -111722,7 +111888,7 @@ atS
aoV
aoV
aoV
-atS
+gXs
aaf
aaa
aaf
diff --git a/_maps/map_files/CogStation/CogStation.dmm b/_maps/map_files/CogStation/CogStation.dmm
index 6f5bab0498..84968dc961 100644
--- a/_maps/map_files/CogStation/CogStation.dmm
+++ b/_maps/map_files/CogStation/CogStation.dmm
@@ -6004,12 +6004,12 @@
c_tag = "Security Checkpoint";
pixel_x = 22
},
-/obj/machinery/newscaster/security_unit{
- pixel_y = 32
- },
/obj/structure/cable{
icon_state = "4-8"
},
+/obj/machinery/airalarm{
+ pixel_y = 23
+ },
/turf/open/floor/plasteel,
/area/security/checkpoint)
"aoz" = (
@@ -32290,10 +32290,6 @@
dir = 8;
light_color = "#e8eaff"
},
-/obj/machinery/airalarm{
- dir = 4;
- pixel_x = -22
- },
/turf/open/floor/plasteel,
/area/hydroponics)
"bsl" = (
@@ -42889,7 +42885,7 @@
/area/science/xenobiology)
"bPd" = (
/obj/structure/table/reinforced,
-/obj/item/book/random/triple,
+/obj/item/book/random,
/turf/open/floor/engine,
/area/science/xenobiology)
"bPe" = (
@@ -71184,6 +71180,13 @@
/obj/machinery/atmospherics/pipe/simple/yellow/visible,
/turf/open/floor/plasteel,
/area/engine/atmos)
+"pMW" = (
+/obj/machinery/airalarm{
+ dir = 1;
+ pixel_y = -22
+ },
+/turf/open/floor/plasteel,
+/area/hydroponics)
"pZq" = (
/obj/structure/disposalpipe/sorting/mail/flip{
dir = 8;
@@ -121382,7 +121385,7 @@ amV
aqI
aqB
aGO
-auy
+pMW
aqB
aqI
amV
diff --git a/_maps/map_files/Deltastation/DeltaStation2.dmm b/_maps/map_files/Deltastation/DeltaStation2.dmm
index 07902b28b1..4e2b009c79 100644
--- a/_maps/map_files/Deltastation/DeltaStation2.dmm
+++ b/_maps/map_files/Deltastation/DeltaStation2.dmm
@@ -47138,13 +47138,9 @@
/turf/open/floor/plasteel,
/area/engine/gravity_generator)
"bEx" = (
-/obj/structure/closet/radiation,
/obj/machinery/light/small{
dir = 1
},
-/obj/structure/extinguisher_cabinet{
- pixel_x = 26
- },
/obj/machinery/atmospherics/pipe/simple/supply/hidden{
dir = 4
},
@@ -48383,7 +48379,10 @@
/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden{
dir = 4
},
-/obj/machinery/light/small,
+/obj/structure/extinguisher_cabinet{
+ pixel_x = 26
+ },
+/obj/structure/closet/radiation,
/turf/open/floor/plasteel,
/area/engine/gravity_generator)
"bGg" = (
@@ -49530,7 +49529,7 @@
},
/obj/machinery/atmospherics/pipe/simple/supply/hidden,
/obj/effect/turf_decal/stripes/line{
- dir = 10
+ dir = 8
},
/turf/open/floor/plasteel,
/area/engine/gravity_generator)
@@ -49543,6 +49542,9 @@
/obj/machinery/atmospherics/pipe/manifold/scrubbers/hidden{
dir = 4
},
+/obj/machinery/light/small{
+ dir = 4
+ },
/turf/open/floor/plasteel,
/area/engine/gravity_generator)
"bHV" = (
@@ -113323,10 +113325,6 @@
/obj/item/clothing/gloves/color/black,
/obj/item/storage/box/evidence,
/obj/item/taperecorder,
-/obj/machinery/airalarm{
- dir = 8;
- pixel_x = 24
- },
/obj/effect/turf_decal/tile/neutral{
dir = 1
},
diff --git a/_maps/map_files/KiloStation/KiloStation.dmm b/_maps/map_files/KiloStation/KiloStation.dmm
index 408efad32f..1b37eea20c 100644
--- a/_maps/map_files/KiloStation/KiloStation.dmm
+++ b/_maps/map_files/KiloStation/KiloStation.dmm
@@ -30529,9 +30529,6 @@
dir = 8
},
/obj/structure/closet/bombcloset,
-/obj/machinery/airalarm{
- pixel_y = 24
- },
/turf/open/floor/plasteel/dark,
/area/science/mixing)
"aXC" = (
@@ -43701,10 +43698,6 @@
/obj/structure/window/reinforced{
dir = 1
},
-/obj/machinery/airalarm{
- dir = 4;
- pixel_x = -22
- },
/mob/living/simple_animal/chicken{
desc = "The arch-nemesis of Kentucky.";
name = "Popeye";
@@ -46363,9 +46356,6 @@
"bwK" = (
/obj/structure/flora/grass/jungle/b,
/obj/structure/flora/ausbushes/sparsegrass,
-/obj/machinery/airalarm{
- pixel_y = 22
- },
/turf/open/floor/grass,
/area/chapel/main)
"bwL" = (
@@ -48132,16 +48122,6 @@
},
/turf/open/floor/plasteel/showroomfloor,
/area/security/brig)
-"bzx" = (
-/obj/effect/turf_decal/tile/neutral,
-/obj/effect/turf_decal/tile/neutral{
- dir = 1
- },
-/obj/effect/turf_decal/tile/neutral{
- dir = 4
- },
-/turf/closed/wall,
-/area/crew_quarters/locker)
"bzy" = (
/obj/machinery/light/small{
dir = 8
@@ -58156,10 +58136,6 @@
icon_state = "plant-02";
pixel_y = 3
},
-/obj/machinery/airalarm{
- dir = 4;
- pixel_x = -22
- },
/obj/machinery/camera{
c_tag = "Prison Wing Cells";
dir = 4;
@@ -61358,7 +61334,7 @@
dir = 10
},
/turf/open/floor/plasteel,
-/area/space)
+/area/security/warden)
"bUs" = (
/obj/machinery/door/firedoor,
/obj/machinery/door/airlock/command/glass{
@@ -67210,10 +67186,6 @@
/obj/structure/cable{
icon_state = "1-4"
},
-/obj/machinery/airalarm{
- dir = 1;
- pixel_y = -22
- },
/turf/open/floor/plasteel,
/area/security/main)
"cdU" = (
@@ -71327,9 +71299,9 @@
icon_gib = "magicarp_gib";
icon_living = "magicarp";
icon_state = "magicarp";
+ maxHealth = 200;
max_co2 = 5;
max_tox = 2;
- maxHealth = 200;
melee_damage_lower = 15;
melee_damage_upper = 20;
min_oxy = 5;
@@ -83281,10 +83253,6 @@
/obj/machinery/atmospherics/pipe/simple/yellow/visible{
dir = 5
},
-/obj/machinery/airalarm{
- dir = 4;
- pixel_x = -23
- },
/obj/machinery/camera{
c_tag = "Atmospherics Port Tanks";
dir = 4;
@@ -108992,7 +108960,7 @@ bss
btV
aEu
bxW
-bzx
+bIV
bKq
bKq
bKq
diff --git a/_maps/map_files/MetaStation/MetaStation.dmm b/_maps/map_files/MetaStation/MetaStation.dmm
index 958ab2dc5a..c2a0e5c353 100644
--- a/_maps/map_files/MetaStation/MetaStation.dmm
+++ b/_maps/map_files/MetaStation/MetaStation.dmm
@@ -1111,10 +1111,6 @@
/turf/open/floor/plasteel,
/area/security/prison)
"acx" = (
-/obj/machinery/airalarm{
- dir = 8;
- pixel_x = 24
- },
/obj/machinery/cryopod{
dir = 8
},
@@ -40497,10 +40493,9 @@
/area/maintenance/port)
"bBr" = (
/obj/machinery/airalarm{
- dir = 4;
- pixel_x = -23
+ dir = 1;
+ pixel_y = -22
},
-/obj/structure/displaycase/trophy,
/turf/open/floor/wood,
/area/library)
"bBs" = (
@@ -45866,10 +45861,6 @@
/area/library)
"bML" = (
/obj/machinery/light/small,
-/obj/machinery/airalarm{
- dir = 1;
- pixel_y = -22
- },
/turf/open/floor/wood,
/area/library)
"bMM" = (
@@ -59581,6 +59572,10 @@
/obj/structure/chair{
dir = 1
},
+/obj/machinery/airalarm{
+ dir = 4;
+ pixel_x = -23
+ },
/turf/open/floor/plasteel/dark,
/area/medical/surgery)
"coz" = (
@@ -60135,9 +60130,6 @@
/obj/structure/cable/yellow{
icon_state = "4-8"
},
-/obj/machinery/atmospherics/pipe/simple/supply/hidden{
- dir = 4
- },
/obj/structure/disposalpipe/junction/flip{
dir = 8
},
@@ -60145,6 +60137,9 @@
/obj/effect/turf_decal/tile/purple{
dir = 4
},
+/obj/machinery/atmospherics/pipe/manifold/supply/hidden{
+ dir = 1
+ },
/turf/open/floor/plasteel/white,
/area/science/research)
"cpx" = (
@@ -60947,9 +60942,6 @@
/obj/structure/cable/yellow{
icon_state = "1-2"
},
-/obj/machinery/atmospherics/pipe/manifold/scrubbers/hidden{
- dir = 1
- },
/obj/structure/disposalpipe/segment,
/obj/effect/turf_decal/tile/purple,
/obj/effect/turf_decal/tile/purple{
@@ -60958,6 +60950,10 @@
/obj/effect/turf_decal/tile/purple{
dir = 8
},
+/obj/machinery/atmospherics/pipe/simple/supply/hidden,
+/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden{
+ dir = 4
+ },
/turf/open/floor/plasteel/white,
/area/science/research)
"cqP" = (
@@ -61465,7 +61461,6 @@
/obj/structure/cable/yellow{
icon_state = "1-2"
},
-/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden,
/obj/machinery/door/firedoor,
/obj/structure/disposalpipe/segment,
/obj/machinery/door/airlock/research{
@@ -61477,6 +61472,7 @@
name = "biohazard containment shutters"
},
/obj/effect/turf_decal/delivery,
+/obj/machinery/atmospherics/pipe/simple/supply/hidden,
/turf/open/floor/plasteel,
/area/science/storage)
"crU" = (
@@ -61595,10 +61591,6 @@
/turf/open/floor/plasteel/white,
/area/medical/surgery)
"csl" = (
-/obj/machinery/airalarm{
- dir = 1;
- pixel_y = -22
- },
/obj/structure/window/reinforced{
dir = 8
},
@@ -62156,11 +62148,11 @@
/obj/structure/cable/yellow{
icon_state = "1-2"
},
-/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden,
/obj/structure/disposalpipe/segment,
/obj/effect/turf_decal/stripes/line{
dir = 9
},
+/obj/machinery/atmospherics/pipe/simple/supply/hidden,
/turf/open/floor/plasteel,
/area/science/storage)
"cth" = (
@@ -62531,24 +62523,21 @@
/obj/structure/cable/yellow{
icon_state = "1-4"
},
-/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden{
- dir = 5
- },
/obj/structure/disposalpipe/segment{
dir = 5
},
/obj/effect/turf_decal/stripes/line{
dir = 8
},
+/obj/machinery/atmospherics/components/unary/vent_pump/on{
+ dir = 1
+ },
/turf/open/floor/plasteel,
/area/science/storage)
"ctY" = (
/obj/structure/cable/yellow{
icon_state = "4-8"
},
-/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden{
- dir = 10
- },
/obj/structure/chair/stool,
/obj/structure/disposalpipe/segment{
dir = 10
@@ -62568,10 +62557,6 @@
/area/science/storage)
"cua" = (
/obj/machinery/portable_atmospherics/canister/oxygen,
-/obj/machinery/airalarm{
- dir = 8;
- pixel_x = 24
- },
/obj/effect/turf_decal/bot,
/turf/open/floor/plasteel,
/area/science/storage)
@@ -63127,7 +63112,6 @@
/turf/open/floor/plasteel,
/area/science/storage)
"cuU" = (
-/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden,
/obj/structure/disposalpipe/segment,
/obj/effect/turf_decal/stripes/line{
dir = 4
@@ -63668,12 +63652,12 @@
/turf/open/floor/plasteel,
/area/science/storage)
"cvW" = (
-/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden,
/obj/structure/disposalpipe/segment,
/obj/effect/landmark/blobstart,
/obj/effect/turf_decal/stripes/line{
dir = 4
},
+/obj/machinery/atmospherics/components/unary/vent_scrubber/on,
/turf/open/floor/plasteel,
/area/science/storage)
"cvX" = (
@@ -68931,13 +68915,13 @@
pixel_x = -25;
pixel_y = -5
},
-/obj/machinery/airalarm{
- dir = 1;
- pixel_y = -22
- },
/obj/effect/turf_decal/stripes/corner{
dir = 4
},
+/obj/machinery/airalarm/unlocked{
+ dir = 1;
+ pixel_y = -22
+ },
/turf/open/floor/plasteel/white,
/area/science/mixing)
"cGm" = (
@@ -102705,7 +102689,7 @@ bue
bwa
bxU
bzD
-bBr
+dmD
bSx
bEw
bzE
@@ -103740,7 +103724,7 @@ bGs
bHS
bzE
bLk
-bzE
+bBr
bue
bPS
bPR
@@ -117641,7 +117625,7 @@ cmQ
cok
cpy
cqQ
-cgq
+crR
cti
ctZ
cuV
diff --git a/_maps/map_files/OmegaStation/OmegaStation.dmm b/_maps/map_files/OmegaStation/OmegaStation.dmm
index f4d798511f..6f7f211b3e 100644
--- a/_maps/map_files/OmegaStation/OmegaStation.dmm
+++ b/_maps/map_files/OmegaStation/OmegaStation.dmm
@@ -4924,11 +4924,11 @@
/obj/effect/turf_decal/stripes/end{
dir = 1
},
-/obj/item/gun/energy/e_gun/hos,
/obj/effect/turf_decal/tile/neutral,
/obj/effect/turf_decal/tile/neutral{
dir = 4
},
+/obj/item/gun/energy/e_gun/hos,
/turf/open/floor/plasteel/dark,
/area/crew_quarters/heads/captain/private)
"ahI" = (
@@ -40544,6 +40544,9 @@
name = "Starboard Quater Maintenance APC";
pixel_y = -26
},
+/obj/structure/cable/white{
+ icon_state = "0-8"
+ },
/turf/open/floor/plating{
icon_state = "panelscorched"
},
diff --git a/_maps/map_files/PubbyStation/PubbyStation.dmm b/_maps/map_files/PubbyStation/PubbyStation.dmm
index 3fb2661cea..12c507c2bf 100644
--- a/_maps/map_files/PubbyStation/PubbyStation.dmm
+++ b/_maps/map_files/PubbyStation/PubbyStation.dmm
@@ -2337,9 +2337,12 @@
/turf/open/floor/plasteel/dark,
/area/security/prison)
"afJ" = (
-/obj/effect/landmark/carpspawn,
-/turf/open/space/basic,
-/area/space/nearstation)
+/obj/effect/turf_decal/tile/neutral,
+/obj/machinery/atmospherics/pipe/manifold/supply/hidden{
+ dir = 8
+ },
+/turf/open/floor/plasteel,
+/area/hallway/primary/central)
"afK" = (
/obj/machinery/atmospherics/components/unary/tank/air{
dir = 1
@@ -16527,6 +16530,9 @@
/obj/structure/cable{
icon_state = "4-8"
},
+/obj/machinery/airalarm{
+ pixel_y = 22
+ },
/turf/open/floor/plasteel,
/area/quartermaster/warehouse)
"aMr" = (
@@ -20027,9 +20033,7 @@
/obj/structure/cable{
icon_state = "1-8"
},
-/obj/machinery/atmospherics/pipe/manifold/scrubbers/hidden{
- dir = 4
- },
+/obj/machinery/atmospherics/pipe/manifold/scrubbers/hidden,
/turf/open/floor/plating,
/area/maintenance/department/crew_quarters/bar)
"aUQ" = (
@@ -20044,6 +20048,9 @@
/obj/effect/turf_decal/tile/neutral{
dir = 8
},
+/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden{
+ dir = 10
+ },
/turf/open/floor/plasteel/dark,
/area/hydroponics)
"aUR" = (
@@ -20485,8 +20492,16 @@
/turf/closed/wall,
/area/janitor)
"aVT" = (
-/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden,
-/turf/closed/wall,
+/obj/machinery/door/firedoor,
+/obj/machinery/door/poddoor/shutters{
+ id = "jangarage";
+ name = "Custodial Closet Shutters"
+ },
+/obj/effect/turf_decal/delivery,
+/obj/machinery/atmospherics/pipe/simple/supply/hidden{
+ dir = 4
+ },
+/turf/open/floor/plasteel,
/area/janitor)
"aVU" = (
/obj/machinery/door/window/eastright{
@@ -20510,6 +20525,7 @@
/obj/effect/turf_decal/tile/neutral{
dir = 8
},
+/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden,
/turf/open/floor/plasteel/dark,
/area/janitor)
"aVV" = (
@@ -20892,9 +20908,6 @@
/obj/machinery/camera{
c_tag = "Custodial Quarters"
},
-/obj/machinery/atmospherics/components/unary/vent_scrubber/on{
- dir = 1
- },
/obj/machinery/light/small{
dir = 1
},
@@ -20915,6 +20928,9 @@
/obj/effect/turf_decal/tile/neutral{
dir = 8
},
+/obj/machinery/atmospherics/components/unary/vent_scrubber/on{
+ dir = 4
+ },
/turf/open/floor/plasteel/dark,
/area/janitor)
"aWO" = (
@@ -20934,6 +20950,9 @@
/obj/effect/turf_decal/tile/neutral{
dir = 8
},
+/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden{
+ dir = 9
+ },
/turf/open/floor/plasteel/dark,
/area/janitor)
"aWP" = (
@@ -23004,9 +23023,6 @@
/turf/open/floor/plasteel,
/area/hallway/primary/central)
"baW" = (
-/obj/machinery/atmospherics/pipe/manifold/supply/hidden{
- dir = 8
- },
/obj/machinery/button/door{
id = "jangarage";
name = "Custodial Closet Shutters Control";
@@ -23014,14 +23030,12 @@
req_access_txt = "26"
},
/obj/effect/turf_decal/tile/neutral,
+/obj/machinery/atmospherics/pipe/simple/supply/hidden,
/turf/open/floor/plasteel,
/area/hallway/primary/central)
"baX" = (
/obj/vehicle/ridden/janicart,
/obj/structure/disposalpipe/segment,
-/obj/machinery/atmospherics/pipe/simple/supply/hidden{
- dir = 4
- },
/obj/machinery/light{
dir = 8
},
@@ -23034,6 +23048,9 @@
/obj/structure/cable{
icon_state = "2-4"
},
+/obj/machinery/atmospherics/pipe/simple/supply/hidden{
+ dir = 6
+ },
/turf/open/floor/plasteel,
/area/janitor)
"baY" = (
@@ -23398,6 +23415,9 @@
/obj/structure/cable{
icon_state = "1-2"
},
+/obj/machinery/atmospherics/pipe/simple/supply/hidden{
+ dir = 9
+ },
/turf/open/floor/plasteel,
/area/janitor)
"bbY" = (
@@ -31173,17 +31193,26 @@
"bup" = (
/obj/machinery/rnd/destructive_analyzer,
/obj/effect/turf_decal/delivery,
+/obj/machinery/atmospherics/pipe/simple/supply/hidden{
+ dir = 4
+ },
/turf/open/floor/plasteel,
/area/science/lab)
"buq" = (
/obj/structure/disposalpipe/segment,
/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden,
/obj/effect/turf_decal/bot,
+/obj/machinery/atmospherics/pipe/simple/supply/hidden{
+ dir = 4
+ },
/turf/open/floor/plasteel,
/area/science/lab)
"bur" = (
/obj/effect/turf_decal/delivery,
/obj/machinery/rnd/production/protolathe/department/science,
+/obj/machinery/atmospherics/pipe/simple/supply/hidden{
+ dir = 4
+ },
/turf/open/floor/plasteel,
/area/science/lab)
"bus" = (
@@ -31202,6 +31231,9 @@
/obj/machinery/light{
dir = 4
},
+/obj/machinery/atmospherics/pipe/simple/supply/hidden{
+ dir = 10
+ },
/turf/open/floor/plasteel/white,
/area/science/lab)
"but" = (
@@ -31664,6 +31696,7 @@
/obj/structure/cable{
icon_state = "1-2"
},
+/obj/machinery/atmospherics/pipe/simple/supply/hidden,
/turf/open/floor/plasteel/white,
/area/science/lab)
"bvA" = (
@@ -32372,6 +32405,7 @@
/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden{
dir = 4
},
+/obj/machinery/atmospherics/pipe/simple/supply/hidden,
/turf/open/floor/plasteel/white,
/area/science/lab)
"bxh" = (
@@ -33056,12 +33090,6 @@
/turf/open/floor/plasteel,
/area/hallway/primary/aft)
"byD" = (
-/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden{
- dir = 6
- },
-/obj/structure/cable{
- icon_state = "2-4"
- },
/obj/machinery/door/airlock/research{
name = "R&D Lab";
req_one_access_txt = "7;29;30"
@@ -33070,6 +33098,12 @@
/obj/structure/disposalpipe/segment{
dir = 6
},
+/obj/structure/cable{
+ icon_state = "4-8"
+ },
+/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden{
+ dir = 4
+ },
/turf/open/floor/plasteel,
/area/science/lab)
"byE" = (
@@ -33091,7 +33125,6 @@
/turf/open/floor/plasteel/white,
/area/science/lab)
"byG" = (
-/obj/machinery/atmospherics/components/unary/vent_pump/on,
/obj/structure/cable{
icon_state = "1-2"
},
@@ -33919,10 +33952,6 @@
/area/hallway/primary/aft)
"bAm" = (
/obj/structure/disposalpipe/segment,
-/obj/structure/cable{
- icon_state = "1-2"
- },
-/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden,
/turf/closed/wall/r_wall,
/area/hallway/primary/aft)
"bAo" = (
@@ -33952,7 +33981,6 @@
/obj/structure/cable{
icon_state = "1-4"
},
-/obj/machinery/atmospherics/pipe/simple/supply/hidden,
/obj/effect/turf_decal/tile/purple{
dir = 1
},
@@ -34404,17 +34432,14 @@
name = "RD Office APC";
pixel_x = -25
},
-/obj/structure/cable{
- icon_state = "0-4"
- },
/obj/effect/turf_decal/tile/purple{
dir = 1
},
/obj/effect/turf_decal/tile/purple{
dir = 8
},
-/obj/machinery/atmospherics/pipe/simple/supply/hidden{
- dir = 4
+/obj/structure/cable{
+ icon_state = "4-8"
},
/turf/open/floor/plasteel/dark,
/area/crew_quarters/heads/hor)
@@ -34425,8 +34450,8 @@
/obj/structure/disposalpipe/segment{
dir = 5
},
-/obj/machinery/atmospherics/pipe/manifold/supply/hidden{
- dir = 4
+/obj/machinery/atmospherics/pipe/simple/supply/hidden{
+ dir = 6
},
/turf/open/floor/plasteel/dark,
/area/crew_quarters/heads/hor)
@@ -34434,6 +34459,9 @@
/obj/structure/disposalpipe/segment{
dir = 4
},
+/obj/machinery/atmospherics/pipe/simple/supply/hidden{
+ dir = 4
+ },
/turf/open/floor/plasteel/dark,
/area/crew_quarters/heads/hor)
"bBt" = (
@@ -34441,6 +34469,9 @@
/obj/structure/disposalpipe/segment{
dir = 4
},
+/obj/machinery/atmospherics/pipe/simple/supply/hidden{
+ dir = 4
+ },
/turf/open/floor/plasteel/dark,
/area/crew_quarters/heads/hor)
"bBu" = (
@@ -34478,6 +34509,9 @@
/obj/effect/turf_decal/tile/purple{
dir = 4
},
+/obj/machinery/atmospherics/pipe/simple/supply/hidden{
+ dir = 9
+ },
/turf/open/floor/plasteel/dark,
/area/crew_quarters/heads/hor)
"bBw" = (
@@ -35240,6 +35274,7 @@
/obj/structure/cable{
icon_state = "0-8"
},
+/obj/machinery/atmospherics/pipe/simple/supply/hidden,
/turf/open/floor/plating,
/area/crew_quarters/heads/hor)
"bCK" = (
@@ -44248,9 +44283,11 @@
/turf/open/floor/engine/co2,
/area/engine/atmos)
"bWh" = (
-/obj/effect/turf_decal/sand,
+/obj/machinery/atmospherics/pipe/manifold/supply/hidden{
+ dir = 8
+ },
/turf/open/floor/plasteel,
-/area/chapel/office)
+/area/hallway/primary/aft)
"bWi" = (
/obj/structure/flora/ausbushes/leafybush,
/obj/structure/flora/ausbushes/reedbush,
@@ -47597,6 +47634,9 @@
pixel_x = -25;
specialfunctions = 4
},
+/obj/machinery/airalarm{
+ pixel_y = 22
+ },
/turf/open/floor/plasteel/grimy,
/area/chapel/main/monastery)
"cgM" = (
@@ -47945,6 +47985,9 @@
pixel_x = -25;
specialfunctions = 4
},
+/obj/machinery/airalarm{
+ pixel_y = 22
+ },
/turf/open/floor/plasteel/grimy,
/area/chapel/main/monastery)
"cip" = (
@@ -51432,6 +51475,10 @@
name = "Coffin Storage";
req_one_access_txt = "22"
},
+/obj/machinery/airalarm{
+ dir = 8;
+ pixel_x = 24
+ },
/turf/open/floor/plasteel/dark,
/area/chapel/main/monastery)
"cvu" = (
@@ -52824,6 +52871,9 @@
/turf/closed/wall/r_wall,
/area/science/lab)
"cCt" = (
+/obj/machinery/atmospherics/pipe/manifold/supply/hidden{
+ dir = 1
+ },
/turf/open/floor/plasteel/white,
/area/science/lab)
"cCB" = (
@@ -53058,6 +53108,9 @@
freq = 1400;
location = "Research Division"
},
+/obj/machinery/atmospherics/pipe/simple/supply/hidden{
+ dir = 4
+ },
/turf/open/floor/plasteel/dark,
/area/science/lab)
"cXW" = (
@@ -53151,10 +53204,6 @@
/area/maintenance/department/security/brig)
"dhz" = (
/obj/structure/disposalpipe/segment,
-/obj/structure/cable{
- icon_state = "1-2"
- },
-/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden,
/obj/structure/chair{
dir = 8;
name = "Defense"
@@ -53163,9 +53212,6 @@
/obj/effect/turf_decal/tile/purple{
dir = 8
},
-/obj/machinery/atmospherics/pipe/simple/supply/hidden{
- dir = 4
- },
/turf/open/floor/plasteel,
/area/hallway/primary/aft)
"dir" = (
@@ -53960,11 +54006,11 @@
/obj/structure/disposalpipe/segment{
dir = 4
},
-/obj/structure/cable{
- icon_state = "4-8"
- },
/obj/machinery/atmospherics/pipe/manifold/scrubbers/hidden{
- dir = 1
+ dir = 4
+ },
+/obj/structure/cable{
+ icon_state = "1-8"
},
/turf/open/floor/plasteel,
/area/hallway/primary/aft)
@@ -55488,6 +55534,12 @@
/obj/effect/turf_decal/tile/purple{
dir = 4
},
+/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden{
+ dir = 6
+ },
+/obj/structure/cable{
+ icon_state = "2-4"
+ },
/turf/open/floor/plasteel,
/area/hallway/primary/aft)
"ioj" = (
@@ -55591,6 +55643,9 @@
/obj/effect/turf_decal/tile/blue{
dir = 8
},
+/obj/structure/cable{
+ icon_state = "4-8"
+ },
/turf/open/floor/plasteel,
/area/hallway/primary/aft)
"iyg" = (
@@ -56345,9 +56400,13 @@
/turf/open/floor/plasteel/white,
/area/science/mixing)
"koz" = (
-/obj/machinery/atmospherics/pipe/manifold/supply/hidden{
+/obj/effect/turf_decal/tile/purple,
+/obj/effect/turf_decal/tile/purple{
dir = 8
},
+/obj/machinery/atmospherics/pipe/simple/supply/hidden{
+ dir = 4
+ },
/turf/open/floor/plasteel,
/area/hallway/primary/aft)
"kpK" = (
@@ -56634,10 +56693,14 @@
/turf/open/floor/plasteel/dark,
/area/science/xenobiology)
"kSb" = (
-/obj/structure/lattice,
-/obj/structure/grille,
-/turf/open/space/basic,
-/area/space)
+/obj/structure/chair/office/light{
+ dir = 8
+ },
+/obj/machinery/atmospherics/components/unary/vent_pump/on{
+ dir = 1
+ },
+/turf/open/floor/plasteel/white,
+/area/science/lab)
"kSF" = (
/obj/structure/cable{
icon_state = "1-4"
@@ -56711,6 +56774,10 @@
/obj/effect/turf_decal/tile/purple{
dir = 8
},
+/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden,
+/obj/structure/cable{
+ icon_state = "1-2"
+ },
/turf/open/floor/plasteel,
/area/hallway/primary/aft)
"lcU" = (
@@ -57121,6 +57188,7 @@
/obj/effect/turf_decal/tile/purple{
dir = 8
},
+/obj/machinery/atmospherics/pipe/simple/supply/hidden,
/turf/open/floor/plasteel/white,
/area/science/lab)
"meF" = (
@@ -57128,8 +57196,9 @@
/turf/closed/wall/r_wall,
/area/engine/supermatter)
"mfC" = (
-/obj/machinery/atmospherics/pipe/simple/supply/hidden{
- dir = 4
+/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden,
+/obj/structure/cable{
+ icon_state = "1-2"
},
/turf/open/floor/plasteel,
/area/hallway/primary/aft)
@@ -59343,6 +59412,7 @@
/area/hallway/secondary/exit/departure_lounge)
"qVk" = (
/obj/machinery/door/poddoor/incinerator_atmos_aux,
+/obj/structure/lattice/catwalk,
/turf/open/space/basic,
/area/maintenance/disposal/incinerator)
"qVP" = (
@@ -59377,6 +59447,10 @@
/obj/effect/turf_decal/tile/yellow{
dir = 8
},
+/obj/machinery/airalarm{
+ dir = 8;
+ pixel_x = 23
+ },
/turf/open/floor/plasteel,
/area/construction/mining/aux_base)
"qXq" = (
@@ -60493,6 +60567,9 @@
id = "research_shutters_2";
name = "research shutters"
},
+/obj/machinery/atmospherics/pipe/simple/supply/hidden{
+ dir = 4
+ },
/turf/open/floor/plasteel,
/area/science/lab)
"tLP" = (
@@ -60815,12 +60892,6 @@
/obj/structure/disposalpipe/segment{
dir = 9
},
-/obj/structure/cable{
- icon_state = "1-8"
- },
-/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden{
- dir = 9
- },
/obj/structure/chair{
dir = 8;
name = "Defense"
@@ -61500,12 +61571,6 @@
},
/turf/open/floor/plasteel,
/area/science/xenobiology)
-"wfG" = (
-/obj/machinery/atmospherics/pipe/simple/supply/hidden{
- dir = 4
- },
-/turf/closed/wall/r_wall,
-/area/crew_quarters/heads/hor)
"wfO" = (
/mob/living/simple_animal/hostile/retaliate/poison/snake,
/turf/open/floor/plating,
@@ -76969,7 +77034,7 @@ aaa
bOv
bNs
bNs
-bWh
+bQg
bQg
bQg
bQg
@@ -84176,7 +84241,7 @@ bIZ
cbb
bDi
ccO
-bIZ
+bva
cjm
cjm
xgh
@@ -84436,8 +84501,8 @@ bva
bva
aht
aht
-kSb
-kSb
+fon
+fon
aht
aht
mau
@@ -86691,7 +86756,7 @@ aXL
aYL
aZN
baW
-aKI
+afJ
aKI
beb
aKI
@@ -86947,8 +87012,8 @@ aVS
aXM
aVS
aVS
-aXM
-bbW
+aVS
+aVT
bbW
aVS
aVS
@@ -87149,7 +87214,7 @@ aaa
aaa
aaa
aaa
-afJ
+cFB
aby
aaa
agQ
@@ -87456,7 +87521,7 @@ aRJ
aSz
aSz
aUP
-aVT
+aVS
aWN
aXO
aYM
@@ -89468,7 +89533,7 @@ aaa
aaa
aaa
aaa
-afJ
+cFB
aaa
aaa
abI
@@ -94932,12 +94997,12 @@ jcT
xje
tTl
tTl
-tTl
+bWh
gkS
tTl
tTl
tTl
-koz
+tTl
dgg
phJ
phJ
@@ -95189,7 +95254,7 @@ bjm
mhn
cqi
cqi
-cqi
+koz
cqi
cqi
imE
@@ -95708,7 +95773,7 @@ duF
bxa
byE
bBp
-wfG
+bBp
bBp
bBp
bBp
@@ -95961,7 +96026,7 @@ cCl
brq
byF
cCt
-byF
+kSb
bxc
nIU
bAo
diff --git a/appveyor.yml b/appveyor.yml
deleted file mode 100644
index c9decee834..0000000000
--- a/appveyor.yml
+++ /dev/null
@@ -1,15 +0,0 @@
-version: '{build}'
-skip_branch_with_pr: true
-shallow_clone: true
-branches:
- except:
- - ___TGS3TempBranch
- - ___TGSTempBranch
-cache:
- - C:\byond\ -> dependencies.sh
-build_script:
- - ps: tools/appveyor/build.ps1
- - ps: "$deployPath = $env:APPVEYOR_BUILD_FOLDER + '/deploy'; bash tools/deploy.sh $deployPath"
- - ps: "[System.IO.Compression.ZipFile]::CreateFromDirectory($env:APPVEYOR_BUILD_FOLDER + '/deploy', $env:APPVEYOR_BUILD_FOLDER + '/deploy.zip')"
-artifacts:
- - path: deploy.zip
diff --git a/code/__DEFINES/MC.dm b/code/__DEFINES/MC.dm
index bad64846d6..cad75fbfe4 100644
--- a/code/__DEFINES/MC.dm
+++ b/code/__DEFINES/MC.dm
@@ -22,49 +22,45 @@
#define START_PROCESSING(Processor, Datum) if (!(Datum.datum_flags & DF_ISPROCESSING)) {Datum.datum_flags |= DF_ISPROCESSING;Processor.processing += Datum}
#define STOP_PROCESSING(Processor, Datum) Datum.datum_flags &= ~DF_ISPROCESSING;Processor.processing -= Datum;Processor.currentrun -= Datum
-//SubSystem flags (Please design any new flags so that the default is off, to make adding flags to subsystems easier)
+//! SubSystem flags (Please design any new flags so that the default is off, to make adding flags to subsystems easier)
-//subsystem does not initialize.
-#define SS_NO_INIT (1<<0)
+/// subsystem does not initialize.
+#define SS_NO_INIT 1
-//subsystem does not fire.
-// (like can_fire = 0, but keeps it from getting added to the processing subsystems list)
-// (Requires a MC restart to change)
-#define SS_NO_FIRE (1<<1)
+/** subsystem does not fire. */
+/// (like can_fire = 0, but keeps it from getting added to the processing subsystems list)
+/// (Requires a MC restart to change)
+#define SS_NO_FIRE 2
-//subsystem only runs on spare cpu (after all non-background subsystems have ran that tick)
-// SS_BACKGROUND has its own priority bracket
-#define SS_BACKGROUND (1<<2)
+/** Subsystem only runs on spare cpu (after all non-background subsystems have ran that tick) */
+/// SS_BACKGROUND has its own priority bracket, this overrides SS_TICKER's priority bump
+#define SS_BACKGROUND 4
-//subsystem does not tick check, and should not run unless there is enough time (or its running behind (unless background))
-#define SS_NO_TICK_CHECK (1<<3)
+/// subsystem does not tick check, and should not run unless there is enough time (or its running behind (unless background))
+#define SS_NO_TICK_CHECK 8
-//Treat wait as a tick count, not DS, run every wait ticks.
-// (also forces it to run first in the tick, above even SS_NO_TICK_CHECK subsystems)
-// (implies all runlevels because of how it works)
-// (overrides SS_BACKGROUND)
-// This is designed for basically anything that works as a mini-mc (like SStimer)
-#define SS_TICKER (1<<4)
+/** Treat wait as a tick count, not DS, run every wait ticks. */
+/// (also forces it to run first in the tick (unless SS_BACKGROUND))
+/// (implies all runlevels because of how it works)
+/// This is designed for basically anything that works as a mini-mc (like SStimer)
+#define SS_TICKER 16
-//keep the subsystem's timing on point by firing early if it fired late last fire because of lag
-// ie: if a 20ds subsystem fires say 5 ds late due to lag or what not, its next fire would be in 15ds, not 20ds.
-#define SS_KEEP_TIMING (1<<5)
+/** keep the subsystem's timing on point by firing early if it fired late last fire because of lag */
+/// ie: if a 20ds subsystem fires say 5 ds late due to lag or what not, its next fire would be in 15ds, not 20ds.
+#define SS_KEEP_TIMING 32
-//Calculate its next fire after its fired.
-// (IE: if a 5ds wait SS takes 2ds to run, its next fire should be 5ds away, not 3ds like it normally would be)
-// This flag overrides SS_KEEP_TIMING
-#define SS_POST_FIRE_TIMING (1<<6)
+/** Calculate its next fire after its fired. */
+/// (IE: if a 5ds wait SS takes 2ds to run, its next fire should be 5ds away, not 3ds like it normally would be)
+/// This flag overrides SS_KEEP_TIMING
+#define SS_POST_FIRE_TIMING 64
-/// Show in stat() by default even if SS_NO_FIRE
-#define SS_ALWAYS_SHOW_STAT (1<<7)
-
-//SUBSYSTEM STATES
-#define SS_IDLE 0 //aint doing shit.
-#define SS_QUEUED 1 //queued to run
-#define SS_RUNNING 2 //actively running
-#define SS_PAUSED 3 //paused by mc_tick_check
-#define SS_SLEEPING 4 //fire() slept.
-#define SS_PAUSING 5 //in the middle of pausing
+//! SUBSYSTEM STATES
+#define SS_IDLE 0 /// ain't doing shit.
+#define SS_QUEUED 1 /// queued to run
+#define SS_RUNNING 2 /// actively running
+#define SS_PAUSED 3 /// paused by mc_tick_check
+#define SS_SLEEPING 4 /// fire() slept.
+#define SS_PAUSING 5 /// in the middle of pausing
#define SUBSYSTEM_DEF(X) GLOBAL_REAL(SS##X, /datum/controller/subsystem/##X);\
/datum/controller/subsystem/##X/New(){\
diff --git a/code/__DEFINES/_extools.dm b/code/__DEFINES/_extools.dm
index 4513243aae..e01e09a3d3 100644
--- a/code/__DEFINES/_extools.dm
+++ b/code/__DEFINES/_extools.dm
@@ -1 +1,38 @@
-#define EXTOOLS (world.system_type == MS_WINDOWS ? "byond-extools.dll" : "libbyond-extools.so")
+// _extools_api.dm - DM API for extools extension library
+// (blatently stolen from rust_g)
+//
+// To configure, create a `extools.config.dm` and set what you care about from
+// the following options:
+//
+// #define EXTOOLS "path/to/extools"
+// Override the .dll/.so detection logic with a fixed path or with detection
+// logic of your own.
+
+#ifndef EXTOOLS
+// Default automatic EXTOOLS detection.
+// On Windows, looks in the standard places for `byond-extools.dll`.
+// On Linux, looks in the standard places for`libbyond-extools.so`.
+
+/* This comment bypasses grep checks */ /var/__extools
+
+/proc/__detect_extools()
+ if (world.system_type == UNIX)
+ if (fexists("./libbyond-extools.so"))
+ // No need for LD_LIBRARY_PATH badness.
+ return __extools = "./libbyond-extools.so"
+ else
+ // It's not in the current directory, so try others
+ return __extools = "libbyond-extools.so"
+ else
+ return __extools = "byond-extools.dll"
+
+#define EXTOOLS (__extools || __detect_extools())
+#endif
+
+#ifndef UNIT_TESTS // use default logging as extools is broken on travis
+#define EXTOOLS_LOGGING // rust_g is used as a fallback if this is undefined
+#endif
+
+/proc/extools_log_write()
+
+/proc/extools_finalize_logging()
diff --git a/code/__DEFINES/_flags/_flags.dm b/code/__DEFINES/_flags/_flags.dm
index 6e018b1eeb..f12e3618f7 100644
--- a/code/__DEFINES/_flags/_flags.dm
+++ b/code/__DEFINES/_flags/_flags.dm
@@ -163,5 +163,19 @@ GLOBAL_LIST_INIT(bitflags, list(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 204
else if(!HAS_TRAIT(x, TRAIT_KEEP_TOGETHER))\
x.appearance_flags &= ~KEEP_TOGETHER
+//dir macros
+///Returns true if the dir is diagonal, false otherwise
+#define ISDIAGONALDIR(d) (d&(d-1))
+///True if the dir is north or south, false therwise
+#define NSCOMPONENT(d) (d&(NORTH|SOUTH))
+///True if the dir is east/west, false otherwise
+#define EWCOMPONENT(d) (d&(EAST|WEST))
+///Flips the dir for north/south directions
+#define NSDIRFLIP(d) (d^(NORTH|SOUTH))
+///Flips the dir for east/west directions
+#define EWDIRFLIP(d) (d^(EAST|WEST))
+///Turns the dir by 180 degrees
+#define DIRFLIP(d) turn(d, 180)
+
/// 33554431 (2^24 - 1) is the maximum value our bitflags can reach.
#define MAX_BITFLAG_DIGITS 8
diff --git a/code/__DEFINES/achievements.dm b/code/__DEFINES/achievements.dm
new file mode 100644
index 0000000000..c39531971c
--- /dev/null
+++ b/code/__DEFINES/achievements.dm
@@ -0,0 +1,108 @@
+// Keep the identifiers here below 32 characters, you can put the full display name in the actual achievement datum
+
+#define ACHIEVEMENT_DEFAULT "default"
+#define ACHIEVEMENT_SCORE "score"
+
+//Misc Medal hub IDs
+#define MEDAL_METEOR "Your Life Before Your Eyes"
+#define MEDAL_PULSE "Jackpot"
+#define MEDAL_TIMEWASTE "Overextended The Joke"
+#define MEDAL_RODSUPLEX "Feat of Strength"
+#define MEDAL_CLOWNCARKING "Round and Full"
+#define MEDAL_THANKSALOT "The Best Driver"
+#define MEDAL_HELBITALJANKEN "Hel-bent on Winning"
+#define MEDAL_MATERIALCRAFT "Getting an Upgrade"
+#define MEDAL_DISKPLEASE "Disk, Please!"
+#define MEDAL_GAMER "I'm Not Important"
+#define MEDAL_VENDORSQUISH "Teenage Anarchist"
+#define MEDAL_SWIRLIE "Bowl-d"
+#define MEDAL_SELFOUCH "Hands???"
+#define MEDAL_SANDMAN "Mister Sandman"
+#define MEDAL_CLEANBOSS "Cleanboss"
+#define MEDAL_RULE8 "Rule 8"
+#define MEDAL_LONGSHIFT "longshift"
+#define MEDAL_SNAIL "KKKiiilll mmmeee"
+#define MEDAL_LOOKOUTSIR "Look Out, Sir!"
+#define MEDAL_GOTTEM "GOTTEM"
+#define MEDAL_ASCENSION "Ascension"
+#define MEDAL_FRENCHING "FrenchingTheBubble"
+#define MEDAL_ASH_ASCENSION "Ash"
+#define MEDAL_FLESH_ASCENSION "Flesh"
+#define MEDAL_RUST_ASCENSION "Rust"
+#define MEDAL_VOID_ASCENSION "Void"
+#define MEDAL_TOOLBOX_SOUL "Toolsoul"
+#define MEDAL_CHEM_TUT "Beginner Chemist"
+
+//Skill medal hub IDs
+#define MEDAL_LEGENDARY_MINER "Legendary Miner"
+
+//Mafia medal hub IDs (wins)
+#define MAFIA_MEDAL_ASSISTANT "Assistant"
+#define MAFIA_MEDAL_DETECTIVE "Detective"
+#define MAFIA_MEDAL_PSYCHOLOGIST "Psychologist"
+#define MAFIA_MEDAL_CHAPLAIN "Chaplain"
+#define MAFIA_MEDAL_MD "Medical Doctor"
+#define MAFIA_MEDAL_OFFICER "Security Officer"
+#define MAFIA_MEDAL_LAWYER "Lawyer"
+#define MAFIA_MEDAL_HOP "Head of Personnel"
+#define MAFIA_MEDAL_HOS "Head of Security"
+#define MAFIA_MEDAL_WARDEN "Warden"
+#define MAFIA_MEDAL_CHANGELING "CHANGELING"
+#define MAFIA_MEDAL_THOUGHTFEEDER "Thoughtfeeder"
+#define MAFIA_MEDAL_TRAITOR "Traitor"
+#define MAFIA_MEDAL_NIGHTMARE "Nightmare"
+#define MAFIA_MEDAL_FUGITIVE "Fugitive"
+#define MAFIA_MEDAL_OBSESSED "Obsessed"
+#define MAFIA_MEDAL_CLOWN "Clown"
+
+//Mafia medal hub IDs (misc stuff)
+#define MAFIA_MEDAL_HATED "Universally Hated"
+#define MAFIA_MEDAL_CHARISMATIC "Charismatic"
+#define MAFIA_MEDAL_VIP "VIP"
+
+//Boss medals
+
+// Medal hub IDs for boss medals (Pre-fixes)
+#define BOSS_MEDAL_ANY "Boss Killer"
+#define BOSS_MEDAL_MINER "Blood-drunk Miner Killer"
+#define BOSS_MEDAL_FROSTMINER "Demonic-frost Miner Killer"
+#define BOSS_MEDAL_BUBBLEGUM "Bubblegum Killer"
+#define BOSS_MEDAL_COLOSSUS "Colossus Killer"
+#define BOSS_MEDAL_DRAKE "Drake Killer"
+#define BOSS_MEDAL_HIEROPHANT "Hierophant Killer"
+#define BOSS_MEDAL_LEGION "Legion Killer"
+#define BOSS_MEDAL_TENDRIL "Tendril Exterminator"
+#define BOSS_MEDAL_SWARMERS "Swarmer Beacon Killer"
+#define BOSS_MEDAL_WENDIGO "Wendigo Killer"
+#define BOSS_MEDAL_KINGGOAT "King Goat Killer"
+
+#define BOSS_MEDAL_MINER_CRUSHER "Blood-drunk Miner Crusher"
+#define BOSS_MEDAL_FROSTMINER_CRUSHER "Demonic-frost Miner Crusher"
+#define BOSS_MEDAL_BUBBLEGUM_CRUSHER "Bubblegum Crusher"
+#define BOSS_MEDAL_COLOSSUS_CRUSHER "Colossus Crusher"
+#define BOSS_MEDAL_DRAKE_CRUSHER "Drake Crusher"
+#define BOSS_MEDAL_HIEROPHANT_CRUSHER "Hierophant Crusher"
+#define BOSS_MEDAL_LEGION_CRUSHER "Legion Crusher"
+#define BOSS_MEDAL_SWARMERS_CRUSHER "Swarmer Beacon Crusher"
+#define BOSS_MEDAL_WENDIGO_CRUSHER "Wendigo Crusher"
+#define BOSS_MEDAL_KINGGOAT_CRUSHER "King Goat Crusher"
+
+// Medal hub IDs for boss-kill scores
+#define BOSS_SCORE "Bosses Killed"
+#define MINER_SCORE "BDMs Killed"
+#define FROST_MINER_SCORE "DFMs Killed"
+#define BUBBLEGUM_SCORE "Bubblegum Killed"
+#define COLOSSUS_SCORE "Colossus Killed"
+#define DRAKE_SCORE "Drakes Killed"
+#define HIEROPHANT_SCORE "Hierophants Killed"
+#define LEGION_SCORE "Legion Killed"
+#define SWARMER_BEACON_SCORE "Swarmer Beacs Killed"
+#define WENDIGO_SCORE "Wendigos Killed"
+#define KINGGOAT_SCORE "King Goat Killed"
+#define TENDRIL_CLEAR_SCORE "Tendrils Killed"
+
+// DB ID for hardcore random mode
+#define HARDCORE_RANDOM_SCORE "Hardcore Random Score"
+
+// DB ID for amount of consumed maintenance pills
+#define MAINTENANCE_PILL_SCORE "Maintenance Pill Score"
diff --git a/code/__DEFINES/admin.dm b/code/__DEFINES/admin.dm
index 143063b4e9..f6293454ee 100644
--- a/code/__DEFINES/admin.dm
+++ b/code/__DEFINES/admin.dm
@@ -91,3 +91,6 @@
#define SPAM_TRIGGER_WARNING 5 //Number of identical messages required before the spam-prevention will warn you to stfu
#define SPAM_TRIGGER_AUTOMUTE 10 //Number of identical messages required before the spam-prevention will automute you
+
+#define STICKYBAN_DB_CACHE_TIME 10 SECONDS
+#define STICKYBAN_ROGUE_CHECK_TIME 5
diff --git a/code/__DEFINES/antagonists.dm b/code/__DEFINES/antagonists.dm
index 68515426c3..e71243994d 100644
--- a/code/__DEFINES/antagonists.dm
+++ b/code/__DEFINES/antagonists.dm
@@ -69,6 +69,9 @@
#define LINGBLOOD_EXPLOSION_THRESHOLD (LINGBLOOD_DETECTION_THRESHOLD * LINGBLOOD_EXPLOSION_MULT) //Hey, important to note here: the explosion threshold is explicitly more than, rather than more than or equal to. This stops a single loud ability from triggering the explosion threshold.
///Heretics --
+GLOBAL_LIST_EMPTY(living_heart_cache) //A list of all living hearts in existance, for us to iterate through.
+
+
#define IS_HERETIC(mob) (mob.mind?.has_antag_datum(/datum/antagonist/heretic))
#define IS_HERETIC_MONSTER(mob) (mob.mind?.has_antag_datum(/datum/antagonist/heretic_monster))
diff --git a/code/__DEFINES/bsql.config.dm b/code/__DEFINES/bsql.config.dm
deleted file mode 100644
index 3f2e8c4d70..0000000000
--- a/code/__DEFINES/bsql.config.dm
+++ /dev/null
@@ -1,6 +0,0 @@
-#define BSQL_EXTERNAL_CONFIGURATION
-#define BSQL_DEL_PROC(path) ##path/Destroy()
-#define BSQL_DEL_CALL(obj) qdel(##obj)
-#define BSQL_IS_DELETED(obj) (QDELETED(obj))
-#define BSQL_PROTECT_DATUM(path) GENERAL_PROTECT_DATUM(##path)
-#define BSQL_ERROR(message) SSdbcore.ReportError(message)
diff --git a/code/__DEFINES/bsql.dm b/code/__DEFINES/bsql.dm
deleted file mode 100644
index 8f2040449a..0000000000
--- a/code/__DEFINES/bsql.dm
+++ /dev/null
@@ -1,135 +0,0 @@
-//BSQL - DMAPI
-#define BSQL_VERSION "v1.3.0.0"
-
-//types of connections
-#define BSQL_CONNECTION_TYPE_MARIADB "MySql"
-#define BSQL_CONNECTION_TYPE_SQLSERVER "SqlServer"
-
-#define BSQL_DEFAULT_TIMEOUT 5
-#define BSQL_DEFAULT_THREAD_LIMIT 50
-
-//Call this before rebooting or shutting down your world to clean up gracefully. This invalidates all active connection and operation datums
-/world/proc/BSQL_Shutdown()
- return
-
-/*
-Called whenever a library call is made with verbose information, override and do with as you please
- message: English debug message
-*/
-/world/proc/BSQL_Debug(msg)
- return
-
-/*
-Create a new database connection, does not perform the actual connect
- connection_type: The BSQL connection_type to use
- asyncTimeout: The timeout to use for normal operations, 0 for infinite, defaults to BSQL_DEFAULT_TIMEOUT
- blockingTimeout: The timeout to use for blocking operations, must be less than or equal to asyncTimeout, 0 for infinite, defaults to asyncTimeout
- threadLimit: The limit of additional threads BSQL will run simultaneously, defaults to BSQL_DEFAULT_THREAD_LIMIT
-*/
-/datum/BSQL_Connection/New(connection_type, asyncTimeout, blockingTimeout, threadLimit)
- return ..()
-
-/*
-Starts an operation to connect to a database. Should only have 1 successful call
- ipaddress: The ip/hostname of the target server
- port: The port of the target server
- username: The username to login to the target server
- password: The password for the target server
- database: Optional database to connect to. Must be used when trying to do database operations, `USE x` is not sufficient
- Returns: A /datum/BSQL_Operation representing the connection or null if an error occurred
-*/
-/datum/BSQL_Connection/proc/BeginConnect(ipaddress, port, username, password, database)
- return
-
-/*
-Properly quotes a string for use by the database. The connection must be open for this proc to succeed
- str: The string to quote
- Returns: The string quoted on success, null on error
-*/
-/datum/BSQL_Connection/proc/Quote(str)
- return
-
-/*
-Starts an operation for a query
- query: The text of the query. Only one query allowed per invocation, no semicolons
- Returns: A /datum/BSQL_Operation/Query representing the running query and subsequent result set or null if an error occurred
-
- Note for MariaDB: The underlying connection is pooled. In order to use connection state based properties (i.e. LAST_INSERT_ID()) you can guarantee multiple queries will use the same connection by running BSQL_DEL_CALL(query) on the finished /datum/BSQL_Operation/Query and then creating the next one with another call to BeginQuery() with no sleeps in between
-*/
-/datum/BSQL_Connection/proc/BeginQuery(query)
- return
-
-/*
-Checks if the operation is complete. This, in some cases must be called multiple times with false return before a result is present regardless of timespan. For best performance check it once per tick
-
- Returns: TRUE if the operation is complete, FALSE if it's not, null on error
-*/
-/datum/BSQL_Operation/proc/IsComplete()
- return
-
-/*
-Blocks the entire game until the given operation completes. IsComplete should not be checked after calling this to avoid potential side effects.
-
-Returns: TRUE on success, FALSE if the operation wait time exceeded the connection's blockingTimeout setting
-*/
-/datum/BSQL_Operation/proc/WaitForCompletion()
- return
-
-/*
-Get the error message associated with an operation. Should not be used while IsComplete() returns FALSE
-
- Returns: The error message, if any. null otherwise
-*/
-/datum/BSQL_Operation/proc/GetError()
- return
-
-/*
-Get the error code associated with an operation. Should not be used while IsComplete() returns FALSE
-
- Returns: The error code, if any. null otherwise
-*/
-/datum/BSQL_Operation/proc/GetErrorCode()
- return
-
-/*
-Gets an associated list of column name -> value representation of the most recent row in the query. Only valid if IsComplete() returns TRUE. If this returns null and no errors are present there are no more results in the query. Important to note that once IsComplete() returns TRUE it must not be called again without checking this or the row values may be lost
-
- Returns: An associated list of column name -> value for the row. Values will always be either strings or null
-*/
-/datum/BSQL_Operation/Query/proc/CurrentRow()
- return
-
-
-/*
-Code configuration options below
-
-Define this to avoid modifying this file but the following defines must be declared somewhere else before BSQL/includes.dm is included
-*/
-#ifndef BSQL_EXTERNAL_CONFIGURATION
-
-//Modify this if you disagree with byond's GC schemes. Ensure this is called for all connections and operations when they are deleted or they will leak native resources until /world/proc/BSQL_Shutdown() is called
-#define BSQL_DEL_PROC(path) ##path/Del()
-
-//The equivalent of calling del() in your codebase
-#define BSQL_DEL_CALL(obj) del(##obj)
-
-//Returns TRUE if an object is delete
-#define BSQL_IS_DELETED(obj) (obj == null)
-
-//Modify this to add protections to the connection and query datums
-#define BSQL_PROTECT_DATUM(path)
-
-//Modify this to change up error handling for the library
-#define BSQL_ERROR(message) CRASH("BSQL: [##message]")
-
-#endif
-
-/*
-Copyright 2018 Jordan Brown
-
-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/code/__DEFINES/cargo.dm b/code/__DEFINES/cargo.dm
index ac5272057a..40a50fe1e6 100644
--- a/code/__DEFINES/cargo.dm
+++ b/code/__DEFINES/cargo.dm
@@ -30,26 +30,36 @@
#define POD_SHAPE_NORML 1
#define POD_SHAPE_OTHER 2
+#define POD_TRANSIT "1"
+#define POD_FALLING "2"
+#define POD_OPENING "3"
+#define POD_LEAVING "4"
+
#define SUPPLYPOD_X_OFFSET -16
+/// The baseline unit for cargo crates. Adjusting this will change the cost of all in-game shuttles, crate export values, bounty rewards, and all supply pack import values, as they use this as their unit of measurement.
+#define CARGO_CRATE_VALUE 200
+
GLOBAL_LIST_EMPTY(supplypod_loading_bays)
GLOBAL_LIST_INIT(podstyles, list(\
- list(POD_SHAPE_NORML, "pod", TRUE, "default", "yellow", RUBBLE_NORMAL, "supply pod", "A Nanotrasen supply drop pod."),\
- list(POD_SHAPE_NORML, "advpod", TRUE, "bluespace", "blue", RUBBLE_NORMAL, "bluespace supply pod" , "A Nanotrasen Bluespace supply pod. Teleports back to CentCom after delivery."),\
- list(POD_SHAPE_NORML, "advpod", TRUE, "centcom", "blue", RUBBLE_NORMAL, "\improper CentCom supply pod", "A Nanotrasen supply pod, this one has been marked with Central Command's designations. Teleports back to CentCom after delivery."),\
- list(POD_SHAPE_NORML, "darkpod", TRUE, "syndicate", "red", RUBBLE_NORMAL, "blood-red supply pod", "An intimidating supply pod, covered in the blood-red markings of the Syndicate. It's probably best to stand back from this."),\
- list(POD_SHAPE_NORML, "darkpod", TRUE, "deathsquad", "blue", RUBBLE_NORMAL, "\improper Deathsquad drop pod", "A Nanotrasen drop pod. This one has been marked the markings of Nanotrasen's elite strike team."),\
- list(POD_SHAPE_NORML, "pod", TRUE, "cultist", "red", RUBBLE_NORMAL, "bloody supply pod", "A Nanotrasen supply pod covered in scratch-marks, blood, and strange runes."),\
- list(POD_SHAPE_OTHER, "missile", FALSE, FALSE, FALSE, RUBBLE_THIN, "cruise missile", "A big ass missile that didn't seem to fully detonate. It was likely launched from some far-off deep space missile silo. There appears to be an auxillery payload hatch on the side, though manually opening it is likely impossible."),\
- list(POD_SHAPE_OTHER, "smissile", FALSE, FALSE, FALSE, RUBBLE_THIN, "\improper Syndicate cruise missile", "A big ass, blood-red missile that didn't seem to fully detonate. It was likely launched from some deep space Syndicate missile silo. There appears to be an auxillery payload hatch on the side, though manually opening it is likely impossible."),\
- list(POD_SHAPE_OTHER, "box", TRUE, FALSE, FALSE, RUBBLE_WIDE, "\improper Aussec supply crate", "An incredibly sturdy supply crate, designed to withstand orbital re-entry. Has 'Aussec Armory - 2532' engraved on the side."),\
- list(POD_SHAPE_NORML, "clownpod", TRUE, "clown", "green", RUBBLE_NORMAL, "\improper HONK pod", "A brightly-colored supply pod. It likely originated from the Clown Federation."),\
- list(POD_SHAPE_OTHER, "orange", TRUE, FALSE, FALSE, RUBBLE_NONE, "\improper Orange", "An angry orange."),\
- list(POD_SHAPE_OTHER, FALSE, FALSE, FALSE, FALSE, RUBBLE_NONE, "\improper S.T.E.A.L.T.H. pod MKVII", "A supply pod that, under normal circumstances, is completely invisible to conventional methods of detection. How are you even seeing this?"),\
- list(POD_SHAPE_OTHER, "gondola", FALSE, FALSE, FALSE, RUBBLE_NONE, "gondola", "The silent walker. This one seems to be part of a delivery agency."),\
- list(POD_SHAPE_OTHER, FALSE, FALSE, FALSE, FALSE, RUBBLE_NONE, FALSE, FALSE, "rl_click", "give_po")\
+ list(POD_SHAPE_NORML, "pod", TRUE, "default", "yellow", RUBBLE_NORMAL, "supply pod", "A Nanotrasen supply drop pod."),\
+ list(POD_SHAPE_NORML, "advpod", TRUE, "bluespace", "blue", RUBBLE_NORMAL, "bluespace supply pod" , "A Nanotrasen Bluespace supply pod. Teleports back to CentCom after delivery."),\
+ list(POD_SHAPE_NORML, "advpod", TRUE, "centcom", "blue", RUBBLE_NORMAL, "\improper CentCom supply pod", "A Nanotrasen supply pod, this one has been marked with Central Command's designations. Teleports back to CentCom after delivery."),\
+ list(POD_SHAPE_NORML, "darkpod", TRUE, "syndicate", "red", RUBBLE_NORMAL, "blood-red supply pod", "An intimidating supply pod, covered in the blood-red markings of the Syndicate. It's probably best to stand back from this."),\
+ list(POD_SHAPE_NORML, "darkpod", TRUE, "deathsquad", "blue", RUBBLE_NORMAL, "\improper Deathsquad drop pod", "A Nanotrasen drop pod. This one has been marked the markings of Nanotrasen's elite strike team."),\
+ list(POD_SHAPE_NORML, "pod", TRUE, "cultist", "red", RUBBLE_NORMAL, "bloody supply pod", "A Nanotrasen supply pod covered in scratch-marks, blood, and strange runes."),\
+ list(POD_SHAPE_OTHER, "missile", FALSE, FALSE, FALSE, RUBBLE_THIN, "cruise missile", "A big ass missile that didn't seem to fully detonate. It was likely launched from some far-off deep space missile silo. There appears to be an auxillery payload hatch on the side, though manually opening it is likely impossible."),\
+ list(POD_SHAPE_OTHER, "smissile", FALSE, FALSE, FALSE, RUBBLE_THIN, "\improper Syndicate cruise missile", "A big ass, blood-red missile that didn't seem to fully detonate. It was likely launched from some deep space Syndicate missile silo. There appears to be an auxillery payload hatch on the side, though manually opening it is likely impossible."),\
+ list(POD_SHAPE_OTHER, "box", TRUE, FALSE, FALSE, RUBBLE_WIDE, "\improper Aussec supply crate", "An incredibly sturdy supply crate, designed to withstand orbital re-entry. Has 'Aussec Armory - 2532' engraved on the side."),\
+ list(POD_SHAPE_NORML, "clownpod", TRUE, "clown", "green", RUBBLE_NORMAL, "\improper HONK pod", "A brightly-colored supply pod. It likely originated from the Clown Federation."),\
+ list(POD_SHAPE_OTHER, "orange", TRUE, FALSE, FALSE, RUBBLE_NONE, "\improper Orange", "An angry orange."),\
+ list(POD_SHAPE_OTHER, FALSE, FALSE, FALSE, FALSE, RUBBLE_NONE, "\improper S.T.E.A.L.T.H. pod MKVII", "A supply pod that, under normal circumstances, is completely invisible to conventional methods of detection. How are you even seeing this?"),\
+ list(POD_SHAPE_OTHER, "gondola", FALSE, FALSE, FALSE, RUBBLE_NONE, "gondola", "The silent walker. This one seems to be part of a delivery agency."),\
+ list(POD_SHAPE_OTHER, FALSE, FALSE, FALSE, FALSE, RUBBLE_NONE, FALSE, FALSE, "rl_click", "give_po")\
))
+
+//cit
#define PACK_GOODY_NONE 0
#define PACK_GOODY_PUBLIC 1 //can be bought by both privates and cargo
#define PACK_GOODY_PRIVATE 2 //can be bought only by privates
diff --git a/code/__DEFINES/colors.dm b/code/__DEFINES/colors.dm
index fb461acfa4..9f45d8da79 100644
--- a/code/__DEFINES/colors.dm
+++ b/code/__DEFINES/colors.dm
@@ -52,3 +52,9 @@
#define COLOR_ASSEMBLY_BLUE "#38559E"
#define COLOR_ASSEMBLY_PURPLE "#6F6192"
#define COLOR_ASSEMBLY_PINK "#ff4adc"
+
+#define COLOR_WHITE "#FFFFFF"
+#define COLOR_VERY_LIGHT_GRAY "#EEEEEE"
+#define COLOR_SILVER "#C0C0C0"
+#define COLOR_GRAY "#808080"
+#define COLOR_HALF_TRANSPARENT_BLACK "#0000007A"
diff --git a/code/__DEFINES/configuration.dm b/code/__DEFINES/configuration.dm
index 0428a16828..9915563cab 100644
--- a/code/__DEFINES/configuration.dm
+++ b/code/__DEFINES/configuration.dm
@@ -22,3 +22,5 @@
#define POLICYCONFIG_ON_DEFIB_LATE "ON_DEFIB_LATE"
/// Displayed to pyroclastic slimes on spawn
#define POLICYCONFIG_ON_PYROCLASTIC_SENTIENT "PYROCLASTIC_SLIME"
+/// Displayed to pAIs on spawn
+#define POLICYCONFIG_PAI "PAI_SPAWN"
diff --git a/code/__DEFINES/dcs/helpers.dm b/code/__DEFINES/dcs/helpers.dm
index 182035db9b..ba2b9a704a 100644
--- a/code/__DEFINES/dcs/helpers.dm
+++ b/code/__DEFINES/dcs/helpers.dm
@@ -6,9 +6,16 @@
#define SEND_GLOBAL_SIGNAL(sigtype, arguments...) ( SEND_SIGNAL(SSdcs, sigtype, ##arguments) )
+/// Signifies that this proc is used to handle signals.
+/// Every proc you pass to RegisterSignal must have this.
+#define SIGNAL_HANDLER SHOULD_NOT_SLEEP(TRUE)
+
+/// Signifies that this proc is used to handle signals, but also sleeps.
+/// Do not use this for new work.
+#define SIGNAL_HANDLER_DOES_SLEEP
+
/// A wrapper for _AddElement that allows us to pretend we're using normal named arguments
#define AddElement(arguments...) _AddElement(list(##arguments))
-
/// A wrapper for _RemoveElement that allows us to pretend we're using normal named arguments
#define RemoveElement(arguments...) _RemoveElement(list(##arguments))
diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm
index a084f2ae34..c12ec20de2 100644
--- a/code/__DEFINES/dcs/signals.dm
+++ b/code/__DEFINES/dcs/signals.dm
@@ -22,7 +22,7 @@
#define COMPONENT_GLOB_BLOCK_CINEMATIC 1
// signals from globally accessible objects
-/// from SSsun when the sun changes position : (azimuth)
+/// from SSsun when the sun changes position : (primary_sun, suns)
#define COMSIG_SUN_MOVED "sun_moved"
//////////////////////////////////////////////////////////////////
@@ -290,7 +290,7 @@
#define COMSIG_LIVING_ACTIVE_BLOCK_START "active_block_start" //from base of mob/living/keybind_start_active_blocking(): (obj/item/blocking_item, list/backup_items)
#define COMPONENT_PREVENT_BLOCK_START 1
-#define COMSIG_LIVING_ACTIVE_PARRY_START "active_parry_start" //from base of mob/living/initiate_parry_sequence(): (parrying_method, datum/parrying_item_mob_or_art, list/backup_items)
+#define COMSIG_LIVING_ACTIVE_PARRY_START "active_parry_start" //from base of mob/living/initiate_parry_sequence(): (parrying_method, datum/parrying_item_mob_or_art, list/backup_items, list/override)
#define COMPONENT_PREVENT_PARRY_START 1
//ALL OF THESE DO NOT TAKE INTO ACCOUNT WHETHER AMOUNT IS 0 OR LOWER AND ARE SENT REGARDLESS!
@@ -541,6 +541,9 @@
#define COMSIG_XENO_TURF_CLICK_CTRL "xeno_turf_click_alt" //from turf AltClickOn(): (/mob)
#define COMSIG_XENO_MONKEY_CLICK_CTRL "xeno_monkey_click_ctrl" //from monkey CtrlClickOn(): (/mob)
+// /datum/element/ventcrawling signals
+#define COMSIG_HANDLE_VENTCRAWL "handle_ventcrawl" //when atom with ventcrawling element attempts to ventcrawl
+#define COMSIG_CHECK_VENTCRAWL "check_ventcrawl" //to check an atom's ventcrawling element tier (if applicable)
// twitch plays
/// Returns direction: (wipe_votes)
#define COMSIG_TWITCH_PLAYS_MOVEMENT_DATA "twitch_plays_movement_data"
diff --git a/code/__DEFINES/economy.dm b/code/__DEFINES/economy.dm
index 746267c15b..715469ff78 100644
--- a/code/__DEFINES/economy.dm
+++ b/code/__DEFINES/economy.dm
@@ -12,6 +12,9 @@
#define MAX_GRANT_SCI 5000
#define MAX_GRANT_SECMEDSRV 3000
+//What should vending machines charge when you buy something in-department.
+#define VENDING_DISCOUNT 0 // price * discount so 0 = 0
+
#define ACCOUNT_CIV "CIV"
#define ACCOUNT_CIV_NAME "Civil Budget"
#define ACCOUNT_ENG "ENG"
diff --git a/code/__DEFINES/instruments.dm b/code/__DEFINES/instruments.dm
index 3c414f87f4..69d2a60e51 100644
--- a/code/__DEFINES/instruments.dm
+++ b/code/__DEFINES/instruments.dm
@@ -19,7 +19,7 @@
#define INSTRUMENT_EXP_FALLOFF_MAX 10
/// Minimum volume for when the sound is considered dead.
-#define INSTRUMENT_MIN_SUSTAIN_DROPOFF 0
+#define INSTRUMENT_MIN_SUSTAIN_DROPOFF 1
#define SUSTAIN_LINEAR 1
#define SUSTAIN_EXPONENTIAL 2
diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm
index ff68f18408..faffea0e6e 100644
--- a/code/__DEFINES/is_helpers.dm
+++ b/code/__DEFINES/is_helpers.dm
@@ -67,17 +67,19 @@ GLOBAL_LIST_INIT(turfs_without_ground, typecacheof(list(
#define isslimeperson(A) (is_species(A, /datum/species/jelly/slime))
#define isluminescent(A) (is_species(A, /datum/species/jelly/luminescent))
#define iszombie(A) (is_species(A, /datum/species/zombie))
+#define isskeleton(A) (is_species(A, /datum/species/skeleton))
+#define ismoth(A) (is_species(A, /datum/species/moth))
#define ishumanbasic(A) (is_species(A, /datum/species/human))
#define iscatperson(A) (ishumanbasic(A) && istype(A.dna.species, /datum/species/human/felinid))
-#define isdwarf(A) (is_species(A, /datum/species/dwarf))
+#define isethereal(A) (is_species(A, /datum/species/ethereal))
+#define isvampire(A) (is_species(A,/datum/species/vampire))
#define isdullahan(A) (is_species(A, /datum/species/dullahan))
+
#define isangel(A) (is_species(A, /datum/species/angel))
-#define isvampire(A) (is_species(A, /datum/species/vampire))
#define ismush(A) (is_species(A, /datum/species/mush))
#define isshadow(A) (is_species(A, /datum/species/shadow))
-#define isskeleton(A) (is_species(A, /datum/species/skeleton))
#define isrobotic(A) (is_species(A, /datum/species/ipc) || is_species(A, /datum/species/synthliz))
-#define isethereal(A) (is_species(A, /datum/species/ethereal))
+#define isdwarf(A) (is_species(A, /datum/species/dwarf))
// Citadel specific species
#define isipcperson(A) (is_species(A, /datum/species/ipc))
@@ -143,6 +145,10 @@ GLOBAL_LIST_INIT(turfs_without_ground, typecacheof(list(
#define ishostile(A) (istype(A, /mob/living/simple_animal/hostile))
+// #define israt(A) (istype(A, /mob/living/simple_animal/hostile/rat))
+
+// #define isregalrat(A) (istype(A, /mob/living/simple_animal/hostile/regalrat))
+
#define isswarmer(A) (istype(A, /mob/living/simple_animal/hostile/swarmer))
#define isguardian(A) (istype(A, /mob/living/simple_animal/hostile/guardian))
@@ -155,6 +161,7 @@ GLOBAL_LIST_INIT(turfs_without_ground, typecacheof(list(
#define isclown(A) (istype(A, /mob/living/simple_animal/hostile/retaliate/clown))
+
//Misc mobs
#define isobserver(A) (istype(A, /mob/dead/observer))
@@ -184,6 +191,8 @@ GLOBAL_LIST_INIT(turfs_without_ground, typecacheof(list(
#define isitem(A) (istype(A, /obj/item))
+#define isstack(A) (istype(A, /obj/item/stack))
+
#define isgrenade(A) (istype(A, /obj/item/grenade))
#define islandmine(A) (istype(A, /obj/effect/mine))
@@ -206,6 +215,8 @@ GLOBAL_LIST_INIT(turfs_without_ground, typecacheof(list(
#define isclothing(A) (istype(A, /obj/item/clothing))
+#define iscash(A) (istype(A, /obj/item/coin) || istype(A, /obj/item/stack/spacecash) || istype(A, /obj/item/holochip))
+
#define isbodypart(A) (istype(A, /obj/item/bodypart))
#define isprojectile(A) (istype(A, /obj/item/projectile))
@@ -240,3 +251,9 @@ GLOBAL_LIST_INIT(glass_sheet_types, typecacheof(list(
#define isshuttleturf(T) (length(T.baseturfs) && (/turf/baseturf_skipover/shuttle in T.baseturfs))
#define isProbablyWallMounted(O) (O.pixel_x > 20 || O.pixel_x < -20 || O.pixel_y > 20 || O.pixel_y < -20)
+#define isbook(O) (is_type_in_typecache(O, GLOB.book_types))
+
+GLOBAL_LIST_INIT(book_types, typecacheof(list(
+ /obj/item/book,
+ /obj/item/spellbook,
+ /obj/item/storage/book)))
diff --git a/code/__DEFINES/language.dm b/code/__DEFINES/language.dm
index add4a8e277..798ea478c2 100644
--- a/code/__DEFINES/language.dm
+++ b/code/__DEFINES/language.dm
@@ -24,3 +24,4 @@
#define LANGUAGE_STONER "stoner"
#define LANGUAGE_VASSAL "vassal"
#define LANGUAGE_VOICECHANGE "voicechange"
+#define LANGUAGE_MULTILINGUAL "multilingual"
diff --git a/code/__DEFINES/loadout.dm b/code/__DEFINES/loadout.dm
index ecd043a66a..973457692e 100644
--- a/code/__DEFINES/loadout.dm
+++ b/code/__DEFINES/loadout.dm
@@ -72,9 +72,11 @@
#define LOADOUT_LIMBS list(LOADOUT_LIMB_NORMAL,LOADOUT_LIMB_PROSTHETIC,LOADOUT_LIMB_AMPUTATED) //you can amputate your legs/arms though
//loadout saving/loading specific defines
-#define MAXIMUM_LOADOUT_SAVES 5
-#define LOADOUT_ITEM "loadout_item"
-#define LOADOUT_COLOR "loadout_color"
+#define MAXIMUM_LOADOUT_SAVES 5
+#define LOADOUT_ITEM "loadout_item"
+#define LOADOUT_COLOR "loadout_color"
+#define LOADOUT_CUSTOM_NAME "loadout_custom_name"
+#define LOADOUT_CUSTOM_DESCRIPTION "loadout_custom_description"
//loadout item flags
#define LOADOUT_CAN_NAME (1<<0) //renaming items
diff --git a/code/__DEFINES/medal.dm b/code/__DEFINES/medal.dm
deleted file mode 100644
index e723c7504e..0000000000
--- a/code/__DEFINES/medal.dm
+++ /dev/null
@@ -1,29 +0,0 @@
-// Medal names
-#define BOSS_KILL_MEDAL "Killer"
-#define ALL_KILL_MEDAL "Exterminator" //Killing all of x type
-#define BOSS_KILL_MEDAL_CRUSHER "Crusher"
-
-//Defines for boss medals
-#define BOSS_MEDAL_MINER "Blood-drunk Miner"
-#define BOSS_MEDAL_BUBBLEGUM "Bubblegum"
-#define BOSS_MEDAL_COLOSSUS "Colossus"
-#define BOSS_MEDAL_DRAKE "Drake"
-#define BOSS_MEDAL_HIEROPHANT "Hierophant"
-#define BOSS_MEDAL_LEGION "Legion"
-#define BOSS_MEDAL_TENDRIL "Tendril"
-#define BOSS_MEDAL_SWARMERS "Swarmer Beacon"
-
-// Score names
-#define HIEROPHANT_SCORE "Hierophants Killed"
-#define BOSS_SCORE "Bosses Killed"
-#define BUBBLEGUM_SCORE "Bubblegum Killed"
-#define COLOSSUS_SCORE "Colossus Killed"
-#define DRAKE_SCORE "Drakes Killed"
-#define LEGION_SCORE "Legion Killed"
-#define SWARMER_BEACON_SCORE "Swarmer Beacons Killed"
-#define TENDRIL_CLEAR_SCORE "Tendrils Killed"
-
-//Misc medals
-#define MEDAL_METEOR "Your Life Before Your Eyes"
-#define MEDAL_PULSE "Jackpot"
-#define MEDAL_TIMEWASTE "Overextended The Joke"
diff --git a/code/__DEFINES/misc.dm b/code/__DEFINES/misc.dm
index 30162594d6..51d1b618fc 100644
--- a/code/__DEFINES/misc.dm
+++ b/code/__DEFINES/misc.dm
@@ -435,8 +435,13 @@ GLOBAL_LIST_INIT(pda_reskins, list(PDA_SKIN_CLASSIC = 'icons/obj/pda.dmi', PDA_S
//text files
#define BRAIN_DAMAGE_FILE "traumas.json"
#define ION_FILE "ion_laws.json"
-#define REDPILL_FILE "redpill.json"
#define PIRATE_NAMES_FILE "pirates.json"
+#define REDPILL_FILE "redpill.json"
+#define ARCADE_FILE "arcade.json"
+// #define BOOMER_FILE "boomer.json"
+// #define LOCATIONS_FILE "locations.json"
+// #define WANTED_FILE "wanted_message.json"
+// #define VISTA_FILE "steve.json"
#define FLESH_SCAR_FILE "wounds/flesh_scar_desc.json"
#define BONE_SCAR_FILE "wounds/bone_scar_desc.json"
#define SCAR_LOC_FILE "wounds/scar_loc.json"
diff --git a/code/__DEFINES/movement.dm b/code/__DEFINES/movement.dm
new file mode 100644
index 0000000000..5bf7de8647
--- /dev/null
+++ b/code/__DEFINES/movement.dm
@@ -0,0 +1,26 @@
+/// The minimum for glide_size to be clamped to.
+#define MIN_GLIDE_SIZE 1
+/// The maximum for glide_size to be clamped to.
+/// This shouldn't be higher than the icon size, and generally you shouldn't be changing this, but it's here just in case.
+#define MAX_GLIDE_SIZE 32
+
+/// Compensating for time dialation
+GLOBAL_VAR_INIT(glide_size_multiplier, 1.0)
+
+///Broken down, here's what this does:
+/// divides the world icon_size (32) by delay divided by ticklag to get the number of pixels something should be moving each tick.
+/// The division result is given a min value of 1 to prevent obscenely slow glide sizes from being set
+/// Then that's multiplied by the global glide size multiplier. 1.25 by default feels pretty close to spot on. This is just to try to get byond to behave.
+/// The whole result is then clamped to within the range above.
+/// Not very readable but it works
+#define DELAY_TO_GLIDE_SIZE(delay) (clamp(((32 / max((delay) / world.tick_lag, 1)) * GLOB.glide_size_multiplier), MIN_GLIDE_SIZE, MAX_GLIDE_SIZE))
+
+/// Enables smooth movement
+// #define SMOOTH_MOVEMENT
+
+/// Set appearance flags in vars
+#ifdef SMOOTH_MOVEMENT
+ #define SET_APPEARANCE_FLAGS(_flags) appearance_flags = (_flags | LONG_GLIDE)
+#else
+ #define SET_APPEARANCE_FLAGS(_flags) appearance_flags = _flags
+#endif
diff --git a/code/__DEFINES/robots.dm b/code/__DEFINES/robots.dm
index a0fded3798..b31fa22962 100644
--- a/code/__DEFINES/robots.dm
+++ b/code/__DEFINES/robots.dm
@@ -8,7 +8,7 @@
#define DEFAULT_SCAN_RANGE 7 //default view range for finding targets.
-//Mode defines
+//Mode defines. If you add a new one make sure you update mode_name in /mob/living/simple_animal/bot
#define BOT_IDLE 0 // idle
#define BOT_HUNT 1 // found target, hunting
#define BOT_PREP_ARREST 2 // at target, preparing to arrest
@@ -27,7 +27,8 @@
#define BOT_NAV 15 // computing navigation
#define BOT_WAIT_FOR_NAV 16 // waiting for nav computation
#define BOT_NO_ROUTE 17 // no destination beacon found (or no route)
-#define BOT_TIPPED 18 // someone tipped a medibot over ;_;
+#define BOT_SHOWERSTANCE 18 // cleaning unhygienic humans
+#define BOT_TIPPED 19 // someone tipped a medibot over ;_;
//Bot types
#define SEC_BOT (1<<0) // Secutritrons (Beepsky) and ED-209s
@@ -37,6 +38,7 @@
#define MED_BOT (1<<4) // Medibots
#define HONK_BOT (1<<5) // Honkbots & ED-Honks
#define FIRE_BOT (1<<6) // Firebots
+#define HYGIENE_BOT (1<<7) // Hygienebots
//AI notification defines
#define NEW_BORG 1
@@ -70,7 +72,14 @@
#define BORG_SEC_AVAILABLE (!CONFIG_GET(flag/disable_secborg) && GLOB.security_level >= CONFIG_GET(number/minimum_secborg_alert))
//silicon_priviledges flags
-#define PRIVILEDGES_SILICON (1<<0)
-#define PRIVILEDGES_PAI (1<<1)
-#define PRIVILEDGES_BOT (1<<2)
-#define PRIVILEDGES_DRONE (1<<3)
+#define PRIVILEGES_SILICON (1<<0)
+#define PRIVILEGES_PAI (1<<1)
+#define PRIVILEGES_BOT (1<<2)
+#define PRIVILEGES_DRONE (1<<3)
+
+#define BORG_LAMP_CD_RESET -1 //special value to reset cyborg's lamp_cooldown
+
+/// Defines for whether or not module slots are broken.
+#define BORG_MODULE_ALL_DISABLED (1<<0)
+#define BORG_MODULE_TWO_DISABLED (1<<1)
+#define BORG_MODULE_THREE_DISABLED (1<<2)
diff --git a/code/__DEFINES/rust_g.dm b/code/__DEFINES/rust_g.dm
index 20e7975ec4..988acd3dae 100644
--- a/code/__DEFINES/rust_g.dm
+++ b/code/__DEFINES/rust_g.dm
@@ -1,19 +1,76 @@
// rust_g.dm - DM API for rust_g extension library
-#define RUST_G "rust_g"
+//
+// To configure, create a `rust_g.config.dm` and set what you care about from
+// the following options:
+//
+// #define RUST_G "path/to/rust_g"
+// Override the .dll/.so detection logic with a fixed path or with detection
+// logic of your own.
+//
+// #define RUSTG_OVERRIDE_BUILTINS
+// Enable replacement rust-g functions for certain builtins. Off by default.
-#define RUSTG_JOB_NO_RESULTS_YET "NO RESULTS YET"
-#define RUSTG_JOB_NO_SUCH_JOB "NO SUCH JOB"
-#define RUSTG_JOB_ERROR "JOB PANICKED"
+#ifndef RUST_G
+// Default automatic RUST_G detection.
+// On Windows, looks in the standard places for `rust_g.dll`.
+// On Linux, looks in `.`, `$LD_LIBRARY_PATH`, and `~/.byond/bin` for either of
+// `librust_g.so` (preferred) or `rust_g` (old).
+
+/* This comment bypasses grep checks */ /var/__rust_g
+
+/proc/__detect_rust_g()
+ if (world.system_type == UNIX)
+ if (fexists("./librust_g.so"))
+ // No need for LD_LIBRARY_PATH badness.
+ return __rust_g = "./librust_g.so"
+ else if (fexists("./rust_g"))
+ // Old dumb filename.
+ return __rust_g = "./rust_g"
+ else if (fexists("[world.GetConfig("env", "HOME")]/.byond/bin/rust_g"))
+ // Old dumb filename in `~/.byond/bin`.
+ return __rust_g = "rust_g"
+ else
+ // It's not in the current directory, so try others
+ return __rust_g = "librust_g.so"
+ else
+ return __rust_g = "rust_g"
+
+#define RUST_G (__rust_g || __detect_rust_g())
+#endif
+
+/**
+ * This proc generates a cellular automata noise grid which can be used in procedural generation methods.
+ *
+ * Returns a single string that goes row by row, with values of 1 representing an alive cell, and a value of 0 representing a dead cell.
+ *
+ * Arguments:
+ * * percentage: The chance of a turf starting closed
+ * * smoothing_iterations: The amount of iterations the cellular automata simulates before returning the results
+ * * birth_limit: If the number of neighboring cells is higher than this amount, a cell is born
+ * * death_limit: If the number of neighboring cells is lower than this amount, a cell dies
+ * * width: The width of the grid.
+ * * height: The height of the grid.
+ */
+#define rustg_cnoise_generate(percentage, smoothing_iterations, birth_limit, death_limit, width, height) \
+ call(RUST_G, "cnoise_generate")(percentage, smoothing_iterations, birth_limit, death_limit, width, height)
#define rustg_dmi_strip_metadata(fname) call(RUST_G, "dmi_strip_metadata")(fname)
#define rustg_dmi_create_png(path, width, height, data) call(RUST_G, "dmi_create_png")(path, width, height, data)
+#define rustg_dmi_resize_png(path, width, height, resizetype) call(RUST_G, "dmi_resize_png")(path, width, height, resizetype)
+
+#define rustg_file_read(fname) call(RUST_G, "file_read")(fname)
+#define rustg_file_exists(fname) call(RUST_G, "file_exists")(fname)
+#define rustg_file_write(text, fname) call(RUST_G, "file_write")(text, fname)
+#define rustg_file_append(text, fname) call(RUST_G, "file_append")(text, fname)
+
+#ifdef RUSTG_OVERRIDE_BUILTINS
+ #define file2text(fname) rustg_file_read("[fname]")
+ #define text2file(text, fname) rustg_file_append(text, "[fname]")
+#endif
#define rustg_git_revparse(rev) call(RUST_G, "rg_git_revparse")(rev)
#define rustg_git_commit_date(rev) call(RUST_G, "rg_git_commit_date")(rev)
-#define rustg_log_write(fname, text, format) call(RUST_G, "log_write")(fname, text, format)
-/proc/rustg_log_close_all() return call(RUST_G, "log_close_all")()
-
#define RUSTG_HTTP_METHOD_GET "get"
#define RUSTG_HTTP_METHOD_PUT "put"
#define RUSTG_HTTP_METHOD_DELETE "delete"
@@ -23,3 +80,22 @@
#define rustg_http_request_blocking(method, url, body, headers) call(RUST_G, "http_request_blocking")(method, url, body, headers)
#define rustg_http_request_async(method, url, body, headers) call(RUST_G, "http_request_async")(method, url, body, headers)
#define rustg_http_check_request(req_id) call(RUST_G, "http_check_request")(req_id)
+
+#define RUSTG_JOB_NO_RESULTS_YET "NO RESULTS YET"
+#define RUSTG_JOB_NO_SUCH_JOB "NO SUCH JOB"
+#define RUSTG_JOB_ERROR "JOB PANICKED"
+
+#define rustg_json_is_valid(text) (call(RUST_G, "json_is_valid")(text) == "true")
+
+#define rustg_log_write(fname, text, format) call(RUST_G, "log_write")(fname, text, format)
+/proc/rustg_log_close_all() return call(RUST_G, "log_close_all")()
+
+#define rustg_noise_get_at_coordinates(seed, x, y) call(RUST_G, "noise_get_at_coordinates")(seed, x, y)
+
+#define rustg_sql_connect_pool(options) call(RUST_G, "sql_connect_pool")(options)
+#define rustg_sql_query_async(handle, query, params) call(RUST_G, "sql_query_async")(handle, query, params)
+#define rustg_sql_query_blocking(handle, query, params) call(RUST_G, "sql_query_blocking")(handle, query, params)
+#define rustg_sql_connected(handle) call(RUST_G, "sql_connected")(handle)
+#define rustg_sql_disconnect_pool(handle) call(RUST_G, "sql_disconnect_pool")(handle)
+#define rustg_sql_check_query(job_id) call(RUST_G, "sql_check_query")("[job_id]")
+
diff --git a/code/__DEFINES/say.dm b/code/__DEFINES/say.dm
index 57484ae85b..9403eca2da 100644
--- a/code/__DEFINES/say.dm
+++ b/code/__DEFINES/say.dm
@@ -86,8 +86,8 @@
#define EMOTE_OMNI 4
//Don't set this very much higher then 1024 unless you like inviting people in to dos your server with message spam
-#define MAX_MESSAGE_LEN 2048 //Citadel edit: What's the WORST that could happen?
-#define MAX_FLAVOR_LEN 4096 //double the maximum message length.
+#define MAX_MESSAGE_LEN 4096 //Citadel edit: What's the WORST that could happen?
+#define MAX_FLAVOR_LEN 4096
#define MAX_TASTE_LEN 40 //lick... vore... ew...
#define MAX_NAME_LEN 42
#define MAX_BROADCAST_LEN 512
diff --git a/code/__DEFINES/sound.dm b/code/__DEFINES/sound.dm
index 73781154c5..1098a07b39 100644
--- a/code/__DEFINES/sound.dm
+++ b/code/__DEFINES/sound.dm
@@ -3,6 +3,7 @@
#define CHANNEL_ADMIN 1023
#define CHANNEL_VOX 1022
#define CHANNEL_JUKEBOX 1021
+
#define CHANNEL_JUKEBOX_START 1016 //The gap between this and CHANNEL_JUKEBOX determines the amount of free jukebox channels. This currently allows 6 jukebox channels to exist.
#define CHANNEL_JUSTICAR_ARK 1015
#define CHANNEL_HEARTBEAT 1014 //sound channel for heartbeats
@@ -15,6 +16,17 @@
#define CHANNEL_DIGEST 1009
#define CHANNEL_PREYLOOP 1008
+///Default range of a sound.
+#define SOUND_RANGE 17
+///default extra range for sounds considered to be quieter
+#define SHORT_RANGE_SOUND_EXTRARANGE -9
+///The range deducted from sound range for things that are considered silent / sneaky
+#define SILENCED_SOUND_EXTRARANGE -11
+///Percentage of sound's range where no falloff is applied
+#define SOUND_DEFAULT_FALLOFF_DISTANCE 1 //For a normal sound this would be 1 tile of no falloff
+///The default exponent of sound falloff
+#define SOUND_FALLOFF_EXPONENT 6
+
//THIS SHOULD ALWAYS BE THE LOWEST ONE!
//KEEP IT UPDATED
@@ -23,6 +35,7 @@
#define MAX_INSTRUMENT_CHANNELS (128 * 6)
#define SOUND_MINIMUM_PRESSURE 10
+/// remove
#define FALLOFF_SOUNDS 1
@@ -53,7 +66,8 @@
#define MINING list('sound/ambience/ambimine.ogg', 'sound/ambience/ambicave.ogg', 'sound/ambience/ambiruin.ogg',\
'sound/ambience/ambiruin2.ogg', 'sound/ambience/ambiruin3.ogg', 'sound/ambience/ambiruin4.ogg',\
'sound/ambience/ambiruin5.ogg', 'sound/ambience/ambiruin6.ogg', 'sound/ambience/ambiruin7.ogg',\
- 'sound/ambience/ambidanger.ogg', 'sound/ambience/ambidanger2.ogg', 'sound/ambience/ambimaint1.ogg', 'sound/ambience/ambilava.ogg')
+ 'sound/ambience/ambidanger.ogg', 'sound/ambience/ambidanger2.ogg', 'sound/ambience/ambimaint1.ogg',\
+ 'sound/ambience/ambilava.ogg')
#define MEDICAL list('sound/ambience/ambinice.ogg')
@@ -80,3 +94,55 @@
'sound/hallucinations/growl3.ogg', 'sound/hallucinations/im_here1.ogg', 'sound/hallucinations/im_here2.ogg', 'sound/hallucinations/i_see_you1.ogg', 'sound/hallucinations/i_see_you2.ogg',\
'sound/hallucinations/look_up1.ogg', 'sound/hallucinations/look_up2.ogg', 'sound/hallucinations/over_here1.ogg', 'sound/hallucinations/over_here2.ogg', 'sound/hallucinations/over_here3.ogg',\
'sound/hallucinations/turn_around1.ogg', 'sound/hallucinations/turn_around2.ogg', 'sound/hallucinations/veryfar_noise.ogg', 'sound/hallucinations/wail.ogg')
+
+
+#define INTERACTION_SOUND_RANGE_MODIFIER -3
+#define EQUIP_SOUND_VOLUME 30
+#define PICKUP_SOUND_VOLUME 15
+#define DROP_SOUND_VOLUME 20
+#define YEET_SOUND_VOLUME 90
+
+
+//default byond sound environments
+#define SOUND_ENVIRONMENT_NONE -1
+#define SOUND_ENVIRONMENT_GENERIC 0
+#define SOUND_ENVIRONMENT_PADDED_CELL 1
+#define SOUND_ENVIRONMENT_ROOM 2
+#define SOUND_ENVIRONMENT_BATHROOM 3
+#define SOUND_ENVIRONMENT_LIVINGROOM 4
+#define SOUND_ENVIRONMENT_STONEROOM 5
+#define SOUND_ENVIRONMENT_AUDITORIUM 6
+#define SOUND_ENVIRONMENT_CONCERT_HALL 7
+#define SOUND_ENVIRONMENT_CAVE 8
+#define SOUND_ENVIRONMENT_ARENA 9
+#define SOUND_ENVIRONMENT_HANGAR 10
+#define SOUND_ENVIRONMENT_CARPETED_HALLWAY 11
+#define SOUND_ENVIRONMENT_HALLWAY 12
+#define SOUND_ENVIRONMENT_STONE_CORRIDOR 13
+#define SOUND_ENVIRONMENT_ALLEY 14
+#define SOUND_ENVIRONMENT_FOREST 15
+#define SOUND_ENVIRONMENT_CITY 16
+#define SOUND_ENVIRONMENT_MOUNTAINS 17
+#define SOUND_ENVIRONMENT_QUARRY 18
+#define SOUND_ENVIRONMENT_PLAIN 19
+#define SOUND_ENVIRONMENT_PARKING_LOT 20
+#define SOUND_ENVIRONMENT_SEWER_PIPE 21
+#define SOUND_ENVIRONMENT_UNDERWATER 22
+#define SOUND_ENVIRONMENT_DRUGGED 23
+#define SOUND_ENVIRONMENT_DIZZY 24
+#define SOUND_ENVIRONMENT_PSYCHOTIC 25
+//If we ever make custom ones add them here
+
+//"sound areas": easy way of keeping different types of areas consistent.
+#define SOUND_AREA_STANDARD_STATION SOUND_ENVIRONMENT_PARKING_LOT
+#define SOUND_AREA_LARGE_ENCLOSED SOUND_ENVIRONMENT_QUARRY
+#define SOUND_AREA_SMALL_ENCLOSED SOUND_ENVIRONMENT_BATHROOM
+#define SOUND_AREA_TUNNEL_ENCLOSED SOUND_ENVIRONMENT_STONEROOM
+#define SOUND_AREA_LARGE_SOFTFLOOR SOUND_ENVIRONMENT_CARPETED_HALLWAY
+#define SOUND_AREA_MEDIUM_SOFTFLOOR SOUND_ENVIRONMENT_LIVINGROOM
+#define SOUND_AREA_SMALL_SOFTFLOOR SOUND_ENVIRONMENT_ROOM
+#define SOUND_AREA_ASTEROID SOUND_ENVIRONMENT_CAVE
+#define SOUND_AREA_SPACE SOUND_ENVIRONMENT_UNDERWATER
+#define SOUND_AREA_LAVALAND SOUND_ENVIRONMENT_MOUNTAINS
+#define SOUND_AREA_ICEMOON SOUND_ENVIRONMENT_CAVE
+#define SOUND_AREA_WOODFLOOR SOUND_ENVIRONMENT_CITY
diff --git a/code/__DEFINES/spaceman_dmm.dm b/code/__DEFINES/spaceman_dmm.dm
index e21f3dc1c1..087fa5e6e6 100644
--- a/code/__DEFINES/spaceman_dmm.dm
+++ b/code/__DEFINES/spaceman_dmm.dm
@@ -29,5 +29,5 @@
#endif
/world/proc/enable_debugger()
- if (fexists(EXTOOLS))
- call(EXTOOLS, "debug_initialize")()
+ if (fexists(EXTOOLS))
+ call(EXTOOLS, "debug_initialize")()
diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm
index b405db83b0..9c67a6b36c 100644
--- a/code/__DEFINES/subsystems.dm
+++ b/code/__DEFINES/subsystems.dm
@@ -1,40 +1,90 @@
-//Update this whenever the db schema changes
-//make sure you add an update to the schema_version stable in the db changelog
+//! Defines for subsystems and overlays
+//!
+//! Lots of important stuff in here, make sure you have your brain switched on
+//! when editing this file
+
+//! ## DB defines
+/**
+ * DB major schema version
+ *
+ * Update this whenever the db schema changes
+ *
+ * make sure you add an update to the schema_version stable in the db changelog
+ */
#define DB_MAJOR_VERSION 4
+
+/**
+ * DB minor schema version
+ *
+ * Update this whenever the db schema changes
+ *
+ * make sure you add an update to the schema_version stable in the db changelog
+ */
#define DB_MINOR_VERSION 7
-//Timing subsystem
-//Don't run if there is an identical unique timer active
-//if the arguments to addtimer are the same as an existing timer, it doesn't create a new timer, and returns the id of the existing timer
+//! ## Timing subsystem
+/**
+ * Don't run if there is an identical unique timer active
+ *
+ * if the arguments to addtimer are the same as an existing timer, it doesn't create a new timer,
+ * and returns the id of the existing timer
+ */
#define TIMER_UNIQUE (1<<0)
-//For unique timers: Replace the old timer rather then not start this one
+
+///For unique timers: Replace the old timer rather then not start this one
#define TIMER_OVERRIDE (1<<1)
-//Timing should be based on how timing progresses on clients, not the sever.
-// tracking this is more expensive,
-// should only be used in conjuction with things that have to progress client side, such as animate() or sound()
+
+/**
+ * Timing should be based on how timing progresses on clients, not the server.
+ *
+ * Tracking this is more expensive,
+ * should only be used in conjuction with things that have to progress client side, such as
+ * animate() or sound()
+ */
#define TIMER_CLIENT_TIME (1<<2)
-//Timer can be stopped using deltimer()
+
+///Timer can be stopped using deltimer()
#define TIMER_STOPPABLE (1<<3)
-//To be used with TIMER_UNIQUE
-//prevents distinguishing identical timers with the wait variable
+
+///prevents distinguishing identical timers with the wait variable
+///
+///To be used with TIMER_UNIQUE
#define TIMER_NO_HASH_WAIT (1<<4)
-//Loops the timer repeatedly until qdeleted
-//In most cases you want a subsystem instead
+
+///Loops the timer repeatedly until qdeleted
+///
+///In most cases you want a subsystem instead, so don't use this unless you have a good reason
#define TIMER_LOOP (1<<5)
-#define TIMER_NO_INVOKE_WARNING 600 //number of byond ticks that are allowed to pass before the timer subsystem thinks it hung on something
-
+///Empty ID define
#define TIMER_ID_NULL -1
-#define INITIALIZATION_INSSATOMS 0 //New should not call Initialize
-#define INITIALIZATION_INNEW_MAPLOAD 2 //New should call Initialize(TRUE)
-#define INITIALIZATION_INNEW_REGULAR 1 //New should call Initialize(FALSE)
+//! ## Initialization subsystem
-#define INITIALIZE_HINT_NORMAL 0 //Nothing happens
-#define INITIALIZE_HINT_LATELOAD 1 //Call LateInitialize
-#define INITIALIZE_HINT_QDEL 2 //Call qdel on the atom
+///New should not call Initialize
+#define INITIALIZATION_INSSATOMS 0
+///New should call Initialize(TRUE)
+#define INITIALIZATION_INNEW_MAPLOAD 2
+///New should call Initialize(FALSE)
+#define INITIALIZATION_INNEW_REGULAR 1
-//type and all subtypes should always call Initialize in New()
+//! ### Initialization hints
+
+///Nothing happens
+#define INITIALIZE_HINT_NORMAL 0
+/**
+ * call LateInitialize at the end of all atom Initalization
+ *
+ * The item will be added to the late_loaders list, this is iterated over after
+ * initalization of subsystems is complete and calls LateInitalize on the atom
+ * see [this file for the LateIntialize proc](atom.html#proc/LateInitialize)
+ */
+#define INITIALIZE_HINT_LATELOAD 1
+
+///Call qdel on the atom after intialization
+#define INITIALIZE_HINT_QDEL 2
+
+///type and all subtypes should always immediately call Initialize in New()
#define INITIALIZE_IMMEDIATE(X) ##X/New(loc, ...){\
..();\
if(!(flags_1 & INITIALIZED_1)) {\
@@ -47,35 +97,40 @@
// Subsystems shutdown in the reverse of the order they initialize in
// The numbers just define the ordering, they are meaningless otherwise.
-#define INIT_ORDER_PROFILER 100
-#define INIT_ORDER_FAIL2TOPIC 99
-#define INIT_ORDER_TITLE 98
-#define INIT_ORDER_GARBAGE 95
-#define INIT_ORDER_DBCORE 94
-#define INIT_ORDER_STATPANELS 93
-#define INIT_ORDER_BLACKBOX 92
-#define INIT_ORDER_SERVER_MAINT 91
-#define INIT_ORDER_INPUT 90
-#define INIT_ORDER_SOUNDS 85
+#define INIT_ORDER_PROFILER 102
+#define INIT_ORDER_FAIL2TOPIC 101
+#define INIT_ORDER_TITLE 100
+#define INIT_ORDER_GARBAGE 99
+#define INIT_ORDER_DBCORE 95
+#define INIT_ORDER_BLACKBOX 94
+#define INIT_ORDER_SERVER_MAINT 93
+#define INIT_ORDER_INPUT 85
+#define INIT_ORDER_SOUNDS 83
+#define INIT_ORDER_INSTRUMENTS 82
#define INIT_ORDER_VIS 80
+#define INIT_ORDER_ACHIEVEMENTS 77
#define INIT_ORDER_RESEARCH 75
#define INIT_ORDER_EVENTS 70
#define INIT_ORDER_JOBS 65
#define INIT_ORDER_QUIRKS 60
#define INIT_ORDER_TICKER 55
-#define INIT_ORDER_INSTRUMENTS 53
+// #define INIT_ORDER_TCG 55
#define INIT_ORDER_MAPPING 50
-#define INIT_ORDER_ECONOMY 45
-#define INIT_ORDER_NETWORKS 40
+#define INIT_ORDER_TIMETRACK 47
+#define INIT_ORDER_NETWORKS 45
+#define INIT_ORDER_ECONOMY 40
#define INIT_ORDER_HOLODECK 35
+// #define INIT_ORDER_OUTPUTS 35
#define INIT_ORDER_ATOMS 30
#define INIT_ORDER_LANGUAGE 25
#define INIT_ORDER_MACHINES 20
#define INIT_ORDER_CIRCUIT 15
+// #define INIT_ORDER_SKILLS 15
#define INIT_ORDER_TIMER 1
#define INIT_ORDER_DEFAULT 0
#define INIT_ORDER_AIR -1
#define INIT_ORDER_AIR_TURFS -2
+#define INIT_ORDER_PERSISTENCE -2 //before assets because some assets take data from SSPersistence
#define INIT_ORDER_MINIMAP -3
#define INIT_ORDER_ASSETS -4
#define INIT_ORDER_ICON_SMOOTHING -5
@@ -86,7 +141,9 @@
#define INIT_ORDER_SHUTTLE -21
#define INIT_ORDER_MINOR_MAPPING -40
#define INIT_ORDER_PATH -50
-#define INIT_ORDER_PERSISTENCE -95
+// #define INIT_ORDER_DISCORD -60
+// #define INIT_ORDER_EXPLOSIONS -69
+#define INIT_ORDER_STATPANELS -98
#define INIT_ORDER_DEMO -99 // o avoid a bunch of changes related to initialization being written, do this last
#define INIT_ORDER_CHAT -100 //Should be last to ensure chat remains smooth during init.
@@ -102,6 +159,7 @@
#define FIRE_PRIORITY_GARBAGE 15
#define FIRE_PRIORITY_WET_FLOORS 20
#define FIRE_PRIORITY_AIR 20
+#define FIRE_PRIORITY_NPC 20
#define FIRE_PRIORITY_PROCESS 25
#define FIRE_PRIORITY_THROWING 25
#define FIRE_PRIORITY_SPACEDRIFT 30
@@ -116,7 +174,6 @@
#define FIRE_PRIORITY_AIR_TURFS 40
#define FIRE_PRIORITY_DEFAULT 50
#define FIRE_PRIORITY_PARALLAX 65
-#define FIRE_PRIORITY_NPC 80
#define FIRE_PRIORITY_MOBS 100
#define FIRE_PRIORITY_TGUI 110
#define FIRE_PRIORITY_PROJECTILES 200
@@ -126,6 +183,8 @@
#define FIRE_PRIORITY_CHAT 400
#define FIRE_PRIORITY_RUNECHAT 410
#define FIRE_PRIORITY_OVERLAYS 500
+// #define FIRE_PRIORITY_EXPLOSIONS 666
+#define FIRE_PRIORITY_TIMER 700
#define FIRE_PRIORITY_INPUT 1000 // This must always always be the max highest priority. Player input must never be lost.
// SS runlevels
@@ -138,6 +197,37 @@
#define RUNLEVELS_DEFAULT (RUNLEVEL_SETUP | RUNLEVEL_GAME | RUNLEVEL_POSTGAME)
+
+
+//! ## Overlays subsystem
+
+///Compile all the overlays for an atom from the cache lists
+// |= on overlays is not actually guaranteed to not add same appearances but we're optimistically using it anyway.
+#define COMPILE_OVERLAYS(A)\
+ do {\
+ var/list/ad = A.add_overlays;\
+ var/list/rm = A.remove_overlays;\
+ if(LAZYLEN(rm)){\
+ A.overlays -= rm;\
+ rm.Cut();\
+ }\
+ if(LAZYLEN(ad)){\
+ A.overlays |= ad;\
+ ad.Cut();\
+ }\
+ A.flags_1 &= ~OVERLAY_QUEUED_1;\
+ } while(FALSE)
+
+
+/**
+ Create a new timer and add it to the queue.
+ * 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
+*/
+#define addtimer(args...) _addtimer(args, file = __FILE__, line = __LINE__)
+
// SSair run section
#define SSAIR_PIPENETS 1
#define SSAIR_ATMOSMACHINERY 2
@@ -148,19 +238,3 @@
#define SSAIR_REBUILD_PIPENETS 7
#define SSAIR_EQUALIZE 8
#define SSAIR_ACTIVETURFS 9
-
-// |= on overlays is not actually guaranteed to not add same appearances but we're optimistically using it anyway.
-#define COMPILE_OVERLAYS(A)\
- if (TRUE) {\
- var/list/ad = A.add_overlays;\
- var/list/rm = A.remove_overlays;\
- if(LAZYLEN(rm)){\
- A.overlays -= rm;\
- A.remove_overlays = null;\
- }\
- if(LAZYLEN(ad)){\
- A.overlays |= ad;\
- A.add_overlays = null;\
- }\
- A.flags_1 &= ~OVERLAY_QUEUED_1;\
- }
diff --git a/code/__DEFINES/tgs.dm b/code/__DEFINES/tgs.dm
index 3225f14d8c..2562bfe4d3 100644
--- a/code/__DEFINES/tgs.dm
+++ b/code/__DEFINES/tgs.dm
@@ -1,6 +1,6 @@
// tgstation-server DMAPI
-#define TGS_DMAPI_VERSION "5.2.10"
+#define TGS_DMAPI_VERSION "5.2.9"
// 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 test merges. Parameters: PR Number, PR Sha, (Nullable) Comment made by TGS user
+/// When the repository merges a pull request. 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
@@ -190,21 +190,21 @@
/// Represents a merge of a GitHub pull request.
/datum/tgs_revision_information/test_merge
- /// The test merge number.
+ /// The pull request number.
var/number
- /// The test merge source's title when it was merged.
+ /// The pull request title when it was merged.
var/title
- /// The test merge source's body when it was merged.
+ /// The pull request body when it was merged.
var/body
- /// The Username of the test merge source's author.
+ /// The GitHub username of the pull request's author.
var/author
- /// An http URL to the test merge source.
+ /// An http URL to the pull request.
var/url
- /// The SHA of the test merge when that was merged.
+ /// The SHA of the pull request when that was merged.
var/pull_request_commit
- /// ISO 8601 timestamp of when the test merge was created on TGS.
+ /// ISO 8601 timestamp of when the pull request was merged.
var/time_merged
- /// Optional comment left by the TGS user who initiated the merge.
+ /// (Nullable) Comment left by the TGS user who initiated the merge..
var/comment
/// Represents a connected chat channel.
diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm
index 2275c4b90b..8e0886a30d 100644
--- a/code/__DEFINES/traits.dm
+++ b/code/__DEFINES/traits.dm
@@ -191,12 +191,14 @@
#define TRAIT_MUSICIAN "musician"
#define TRAIT_PERMABONER "permanent_arousal"
#define TRAIT_NEVERBONER "never_aroused"
+#define TRAIT_NYMPHO "nymphomaniac"
#define TRAIT_MASO "masochism"
#define TRAIT_HIGH_BLOOD "high_blood"
#define TRAIT_PARA "paraplegic"
#define TRAIT_EMPATH "empath"
#define TRAIT_FRIENDLY "friendly"
#define TRAIT_SNOB "snob"
+#define TRAIT_MULTILINGUAL "multilingual"
#define TRAIT_CULT_EYES "cult_eyes"
#define TRAIT_AUTO_CATCH_ITEM "auto_catch_item"
#define TRAIT_CLOWN_MENTALITY "clown_mentality" // The future is now, clownman.
@@ -253,6 +255,7 @@
// item traits
#define TRAIT_NODROP "nodrop"
+#define TRAIT_SPOOKY_THROW "spooky_throw"
// common trait sources
#define TRAIT_GENERIC "generic"
diff --git a/code/__DEFINES/vote.dm b/code/__DEFINES/vote.dm
index a3617e21d0..88e70b884e 100644
--- a/code/__DEFINES/vote.dm
+++ b/code/__DEFINES/vote.dm
@@ -2,7 +2,7 @@
#define APPROVAL_VOTING "APPROVAL"
#define SCHULZE_VOTING "SCHULZE"
#define SCORE_VOTING "SCORE"
-#define MAJORITY_JUDGEMENT_VOTING "MAJORITY_JUDGEMENT"
+#define HIGHEST_MEDIAN_VOTING "HIGHEST_MEDIAN"
#define INSTANT_RUNOFF_VOTING "IRV"
#define SHOW_RESULTS (1<<0)
@@ -18,7 +18,7 @@ GLOBAL_LIST_INIT(vote_type_names,list(\
"IRV (single winner ranked choice)" = INSTANT_RUNOFF_VOTING,\
"Schulze (ranked choice, higher result=better)" = SCHULZE_VOTING,\
"Raw Score (returns results from 0 to 1, winner is 1)" = SCORE_VOTING,\
-"Majority Judgement (single-winner score voting)" = MAJORITY_JUDGEMENT_VOTING,\
+"Highest Median (single-winner score voting)" = HIGHEST_MEDIAN_VOTING,\
))
GLOBAL_LIST_INIT(display_vote_settings, list(\
diff --git a/code/__DEFINES/vv.dm b/code/__DEFINES/vv.dm
index fe46fdc710..99a2e9d0ab 100644
--- a/code/__DEFINES/vv.dm
+++ b/code/__DEFINES/vv.dm
@@ -90,6 +90,9 @@
#define VV_HK_TRIGGER_EMP "empulse"
#define VV_HK_TRIGGER_EXPLOSION "explode"
#define VV_HK_AUTO_RENAME "auto_rename"
+// #define VV_HK_RADIATE "radiate"
+#define VV_HK_EDIT_FILTERS "edit_filters"
+// #define VV_HK_ADD_AI "add_ai"
// /obj
#define VV_HK_OSAY "osay"
diff --git a/code/__HELPERS/_extools_api.dm b/code/__HELPERS/_extools_api.dm
deleted file mode 100644
index af348dc939..0000000000
--- a/code/__HELPERS/_extools_api.dm
+++ /dev/null
@@ -1,5 +0,0 @@
-#define EXTOOLS_LOGGING // rust_g is used as a fallback if this is undefined
-
-/proc/extools_log_write()
-
-/proc/extools_finalize_logging()
diff --git a/code/__HELPERS/_lists.dm b/code/__HELPERS/_lists.dm
index 3efb50ef65..31f34c5d4c 100644
--- a/code/__HELPERS/_lists.dm
+++ b/code/__HELPERS/_lists.dm
@@ -37,7 +37,7 @@
* 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 the current bin item to compare against COMPARE is fetched. By key or value.
+ * COMPTYPE: How should the values be compared? Either COMPARE_KEY or COMPARE_VALUE.
*/
#define BINARY_INSERT(INPUT, LIST, TYPECONT, COMPARE, COMPARISON, COMPTYPE) \
do {\
@@ -49,7 +49,7 @@
var/__BIN_LEFT = 1;\
var/__BIN_RIGHT = __BIN_CTTL;\
var/__BIN_MID = (__BIN_LEFT + __BIN_RIGHT) >> 1;\
- var/##TYPECONT/__BIN_ITEM;\
+ var ##TYPECONT/__BIN_ITEM;\
while(__BIN_LEFT < __BIN_RIGHT) {\
__BIN_ITEM = COMPTYPE;\
if(__BIN_ITEM.##COMPARISON <= COMPARE.##COMPARISON) {\
@@ -66,25 +66,54 @@
} while(FALSE)
//Returns a list in plain english as a string
-/proc/english_list(list/input, nothing_text = "nothing", and_text = " and ", comma_text = ", ", final_comma_text = "" )
- var/total = input.len
- if (!total)
- return nothing_text
- else if (total == 1)
- return "[input[1]]"
- else if (total == 2)
- return "[input[1]][and_text][input[2]]"
- else
- var/output = ""
- var/index = 1
- while (index < total)
- if (index == total - 1)
- comma_text = final_comma_text
+/proc/english_list(list/input, nothing_text = "nothing", and_text = " and ", comma_text = ", ", final_comma_text = "")
+ var/total = length(input)
+ switch(total)
+ if (0)
+ return "[nothing_text]"
+ if (1)
+ return "[input[1]]"
+ if (2)
+ return "[input[1]][and_text][input[2]]"
+ else
+ var/output = ""
+ var/index = 1
+ while (index < total)
+ if (index == total - 1)
+ comma_text = final_comma_text
- output += "[input[index]][comma_text]"
- index++
+ output += "[input[index]][comma_text]"
+ index++
- return "[output][and_text][input[index]]"
+ return "[output][and_text][input[index]]"
+
+/**
+ * English_list but associative supporting. Higher overhead.
+ */
+/proc/english_list_assoc(list/input, nothing_text = "nothing", and_text = " and ", comma_text = ", ", final_comma_text = "")
+ var/total = length(input)
+ switch(total)
+ if (0)
+ return "[nothing_text]"
+ if (1)
+ var/assoc = input[input[1]] == null? "" : " = [input[input[1]]]"
+ return "[input[1]][assoc]"
+ if (2)
+ var/assoc = input[input[1]] == null? "" : " = [input[input[1]]]"
+ var/assoc2 = input[input[2]] == null? "" : " = [input[input[2]]]"
+ return "[input[1]][assoc][and_text][input[2]][assoc2]"
+ else
+ var/output = ""
+ var/index = 1
+ var/assoc
+ while (index < total)
+ if (index == total - 1)
+ comma_text = final_comma_text
+ assoc = input[input[index]] == null? "" : " = [input[input[index]]]"
+ output += "[input[index]][assoc][comma_text]"
+ ++index
+ assoc = input[input[index]] == null? "" : " = [input[input[index]]]"
+ return "[output][and_text][input[index]]"
//Returns list element or null. Should prevent "index out of bounds" error.
/proc/listgetindex(list/L, index)
@@ -585,7 +614,7 @@
used_key_list[input_key] = 1
return input_key
-#if DM_VERSION > 513
+#if DM_VERSION > 514
#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
diff --git a/code/__HELPERS/_logging.dm b/code/__HELPERS/_logging.dm
index 76ca97cd3a..8464e373d5 100644
--- a/code/__HELPERS/_logging.dm
+++ b/code/__HELPERS/_logging.dm
@@ -32,7 +32,7 @@
#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)
@@ -191,6 +191,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, .)
+
/proc/log_reagent(text)
WRITE_LOG(GLOB.reagent_log, text)
@@ -227,10 +231,10 @@
src_object = window.locked_by.src_object
// Insert src_object info
if(src_object)
- entry += "\nUsing: [src_object.type] [REF(src_object)]"
+ entry += "Using: [src_object.type] [REF(src_object)]"
// Insert message
if(message)
- entry += "\n[message]"
+ entry += "[message]"
WRITE_LOG(GLOB.tgui_log, entry)
/* Close open log handles. This should be called as late as possible, and no logging should hapen after. */
diff --git a/code/__HELPERS/chat.dm b/code/__HELPERS/chat.dm
new file mode 100644
index 0000000000..57824b6286
--- /dev/null
+++ b/code/__HELPERS/chat.dm
@@ -0,0 +1,74 @@
+/*
+
+Here's how to use the chat system with configs
+
+send2adminchat is a simple function that broadcasts to admin channels
+
+send2chat is a bit verbose but can be very specific
+
+The second parameter is a string, this string should be read from a config.
+What this does is dictacte which TGS4 channels can be sent to.
+
+For example if you have the following channels in tgs4 set up
+- Channel 1, Tag: asdf
+- Channel 2, Tag: bombay,asdf
+- Channel 3, Tag: Hello my name is asdf
+- Channel 4, No Tag
+- Channel 5, Tag: butts
+
+and you make the call:
+
+send2chat("I sniff butts", CONFIG_GET(string/where_to_send_sniff_butts))
+
+and the config option is set like:
+
+WHERE_TO_SEND_SNIFF_BUTTS asdf
+
+It will be sent to channels 1 and 2
+
+Alternatively if you set the config option to just:
+
+WHERE_TO_SEND_SNIFF_BUTTS
+
+it will be sent to all connected chats.
+
+In TGS3 it will always be sent to all connected designated game chats.
+*/
+
+/**
+ * Sends a message to TGS chat channels.
+ *
+ * message - The message to send.
+ * channel_tag - Required. If "", the message with be sent to all connected (Game-type for TGS3) channels. Otherwise, it will be sent to TGS4 channels with that tag (Delimited by ','s).
+ */
+/proc/send2chat(message, channel_tag)
+ if(channel_tag == null || !world.TgsAvailable())
+ return
+
+ var/datum/tgs_version/version = world.TgsVersion()
+ if(channel_tag == "" || version.suite == 3)
+ world.TgsTargetedChatBroadcast(message, FALSE)
+ return
+
+ var/list/channels_to_use = list()
+ for(var/I in world.TgsChatChannelInfo())
+ var/datum/tgs_chat_channel/channel = I
+ var/list/applicable_tags = splittext(channel.custom_tag, ",")
+ if(channel_tag in applicable_tags)
+ channels_to_use += channel
+
+ if(channels_to_use.len)
+ world.TgsChatBroadcast(message, channels_to_use)
+
+/**
+ * Sends a message to TGS admin chat channels.
+ *
+ * category - The category of the mssage.
+ * message - The message to send.
+ */
+/proc/send2adminchat(category, message, embed_links = FALSE)
+ category = replacetext(replacetext(category, "\proper", ""), "\improper", "")
+ message = replacetext(replacetext(message, "\proper", ""), "\improper", "")
+ // if(!embed_links)
+ // message = GLOB.has_discord_embeddable_links.Replace(replacetext(message, "`", ""), " ```$1``` ")
+ world.TgsTargetedChatBroadcast("[category] | [message]", TRUE)
diff --git a/code/__HELPERS/filters.dm b/code/__HELPERS/filters.dm
new file mode 100644
index 0000000000..7be7ca5d73
--- /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/names.dm b/code/__HELPERS/names.dm
index a61e3a6492..8ff610e68c 100644
--- a/code/__HELPERS/names.dm
+++ b/code/__HELPERS/names.dm
@@ -19,22 +19,6 @@
/proc/arachnid_name()
return "[pick(GLOB.arachnid_first)] [pick(GLOB.arachnid_last)]"
-/proc/church_name()
- var/static/church_name
- if (church_name)
- return church_name
-
- var/name = ""
-
- name += pick("Holy", "United", "First", "Second", "Last")
-
- if (prob(20))
- name += " Space"
-
- name += " " + pick("Church", "Cathedral", "Body", "Worshippers", "Movement", "Witnesses")
- name += " of [religion_name()]"
-
- return name
GLOBAL_VAR(command_name)
/proc/command_name()
@@ -52,17 +36,6 @@ GLOBAL_VAR(command_name)
return name
-/proc/religion_name()
- var/static/religion_name
- if (religion_name)
- return religion_name
-
- var/name = ""
-
- name += pick("bee", "science", "edu", "captain", "assistant", "monkey", "alien", "space", "unit", "sprocket", "gadget", "bomb", "revolution", "beyond", "station", "goon", "robot", "ivor", "hobnob")
- name += pick("ism", "ia", "ology", "istism", "ites", "ick", "ian", "ity")
-
- return capitalize(name)
/proc/station_name()
if(!GLOB.station_name)
diff --git a/code/__HELPERS/priority_announce.dm b/code/__HELPERS/priority_announce.dm
index d06912b701..374e89f715 100644
--- a/code/__HELPERS/priority_announce.dm
+++ b/code/__HELPERS/priority_announce.dm
@@ -10,7 +10,7 @@
announcement += "
[html_encode(title)]
"
else if(type == "Captain")
announcement += "Captain Announces
"
- GLOB.news_network.SubmitArticle(text, "Captain's Announcement", "Station Announcements", null)
+ GLOB.news_network.SubmitArticle(html_encode(text), "Captain's Announcement", "Station Announcements", null)
else
if(!sender_override)
diff --git a/code/__HELPERS/roundend.dm b/code/__HELPERS/roundend.dm
index a860a8cd3f..afd8a7c223 100644
--- a/code/__HELPERS/roundend.dm
+++ b/code/__HELPERS/roundend.dm
@@ -1,81 +1,102 @@
-#define POPCOUNT_SURVIVORS "survivors" //Not dead at roundend
-#define POPCOUNT_ESCAPEES "escapees" //Not dead and on centcom/shuttles marked as escaped
-#define POPCOUNT_SHUTTLE_ESCAPEES "shuttle_escapees" //Emergency shuttle only.
+#define POPCOUNT_SURVIVORS "survivors" //Not dead at roundend
+#define POPCOUNT_ESCAPEES "escapees" //Not dead and on centcom/shuttles marked as escaped
+#define POPCOUNT_SHUTTLE_ESCAPEES "shuttle_escapees" //Emergency shuttle only.
+#define PERSONAL_LAST_ROUND "personal last round"
+#define SERVER_LAST_ROUND "server last round"
/datum/controller/subsystem/ticker/proc/gather_roundend_feedback()
- var/datum/station_state/end_state = new /datum/station_state()
- end_state.count()
- station_integrity = min(PERCENT(GLOB.start_state.score(end_state)), 100)
gather_antag_data()
record_nuke_disk_location()
var/json_file = file("[GLOB.log_directory]/round_end_data.json")
+ // All but npcs sublists and ghost category contain only mobs with minds
var/list/file_data = list("escapees" = list("humans" = list(), "silicons" = list(), "others" = list(), "npcs" = list()), "abandoned" = list("humans" = list(), "silicons" = list(), "others" = list(), "npcs" = list()), "ghosts" = list(), "additional data" = list())
- var/num_survivors = 0
- var/num_escapees = 0
- var/num_shuttle_escapees = 0
+ var/num_survivors = 0 //Count of non-brain non-camera mobs with mind that are alive
+ var/num_escapees = 0 //Above and on centcom z
+ var/num_shuttle_escapees = 0 //Above and on escape shuttle
var/list/area/shuttle_areas
- if(SSshuttle && SSshuttle.emergency)
+ if(SSshuttle?.emergency)
shuttle_areas = SSshuttle.emergency.shuttle_areas
- for(var/mob/m in GLOB.mob_list)
- var/escaped
- var/category
+
+ for(var/mob/M in GLOB.mob_list)
var/list/mob_data = list()
- if(isnewplayer(m))
+ if(isnewplayer(M))
continue
- if (m.client && m.client.prefs && m.client.prefs.auto_ooc)
- if (!(m.client.prefs.chat_toggles & CHAT_OOC))
- m.client.prefs.chat_toggles ^= CHAT_OOC
- if(m.mind)
- if(m.stat != DEAD && !isbrain(m) && !iscameramob(m))
+ // enable their ooc?
+ if (M.client?.prefs?.auto_ooc)
+ if (!(M.client.prefs.chat_toggles & CHAT_OOC))
+ M.client.prefs.chat_toggles ^= CHAT_OOC
+
+ var/escape_status = "abandoned" //default to abandoned
+ var/category = "npcs" //Default to simple count only bracket
+ var/count_only = TRUE //Count by name only or full info
+
+ mob_data["name"] = M.name
+ if(M.mind)
+ count_only = FALSE
+ mob_data["ckey"] = M.mind.key
+ if(M.stat != DEAD && !isbrain(M) && !iscameramob(M))
num_survivors++
- mob_data += list("name" = m.name, "ckey" = ckey(m.mind.key))
- if(isobserver(m))
- escaped = "ghosts"
- else if(isliving(m))
- var/mob/living/L = m
- mob_data += list("location" = get_area(L), "health" = L.health)
+ if(EMERGENCY_ESCAPED_OR_ENDGAMED && (M.onCentCom() || M.onSyndieBase()))
+ num_escapees++
+ escape_status = "escapees"
+ if(shuttle_areas[get_area(M)])
+ num_shuttle_escapees++
+ if(isliving(M))
+ var/mob/living/L = M
+ mob_data["location"] = get_area(L)
+ mob_data["health"] = L.health
if(ishuman(L))
var/mob/living/carbon/human/H = L
category = "humans"
- mob_data += list("job" = H.mind.assigned_role, "species" = H.dna.species.name)
+ if(H.mind)
+ mob_data["job"] = H.mind.assigned_role
+ else
+ mob_data["job"] = "Unknown"
+ mob_data["species"] = H.dna.species.name
else if(issilicon(L))
category = "silicons"
if(isAI(L))
- mob_data += list("module" = "AI")
- if(isAI(L))
- mob_data += list("module" = "pAI")
- if(iscyborg(L))
+ mob_data["module"] = "AI"
+ else if(ispAI(L))
+ mob_data["module"] = "pAI"
+ else if(iscyborg(L))
var/mob/living/silicon/robot/R = L
- mob_data += list("module" = R.module)
- else
- category = "others"
- mob_data += list("typepath" = m.type)
- if(!escaped)
- if(EMERGENCY_ESCAPED_OR_ENDGAMED && (m.onCentCom() || m.onSyndieBase()))
- escaped = "escapees"
- num_escapees++
- if(shuttle_areas[get_area(m)])
- num_shuttle_escapees++
- else
- escaped = "abandoned"
- if(!m.mind && (!ishuman(m) || !issilicon(m)))
- var/list/npc_nest = file_data["[escaped]"]["npcs"]
- if(npc_nest.Find(initial(m.name)))
- file_data["[escaped]"]["npcs"]["[initial(m.name)]"] += 1
- else
- file_data["[escaped]"]["npcs"]["[initial(m.name)]"] = 1
- else
- if(isobserver(m))
- var/pos = length(file_data["[escaped]"]) + 1
- file_data["[escaped]"]["[pos]"] = mob_data
- else
- if(!category)
+ mob_data["module"] = R.module.name
+ else
category = "others"
- mob_data += list("name" = m.name, "typepath" = m.type)
- var/pos = length(file_data["[escaped]"]["[category]"]) + 1
- file_data["[escaped]"]["[category]"]["[pos]"] = mob_data
+ mob_data["typepath"] = M.type
+ //Ghosts don't care about minds, but we want to retain ckey data etc
+ if(isobserver(M))
+ count_only = FALSE
+ escape_status = "ghosts"
+ if(!M.mind)
+ mob_data["ckey"] = M.key
+ category = null //ghosts are one list deep
+ //All other mindless stuff just gets counts by name
+ if(count_only)
+ var/list/npc_nest = file_data["[escape_status]"]["npcs"]
+ var/name_to_use = initial(M.name)
+ if(ishuman(M))
+ name_to_use = "Unknown Human" //Monkeymen and other mindless corpses
+ if(npc_nest.Find(name_to_use))
+ file_data["[escape_status]"]["npcs"][name_to_use] += 1
+ else
+ file_data["[escape_status]"]["npcs"][name_to_use] = 1
+ else
+ //Mobs with minds and ghosts get detailed data
+ if(category)
+ var/pos = length(file_data["[escape_status]"]["[category]"]) + 1
+ file_data["[escape_status]"]["[category]"]["[pos]"] = mob_data
+ else
+ var/pos = length(file_data["[escape_status]"]) + 1
+ file_data["[escape_status]"]["[pos]"] = mob_data
+
+ var/datum/station_state/end_state = new /datum/station_state()
+ end_state.count()
+ station_integrity = min(PERCENT(GLOB.start_state.score(end_state)), 100)
file_data["additional data"]["station integrity"] = station_integrity
WRITE_FILE(json_file, json_encode(file_data))
+
SSblackbox.record_feedback("nested tally", "round_end_stats", num_survivors, list("survivors", "total"))
SSblackbox.record_feedback("nested tally", "round_end_stats", num_escapees, list("escapees", "total"))
SSblackbox.record_feedback("nested tally", "round_end_stats", GLOB.joined_player_list.len, list("players", "total"))
@@ -169,10 +190,34 @@
file_data["wanted"] = list("author" = "[GLOB.news_network.wanted_issue.scannedUser]", "criminal" = "[GLOB.news_network.wanted_issue.criminal]", "description" = "[GLOB.news_network.wanted_issue.body]", "photo file" = "[GLOB.news_network.wanted_issue.photo_file]")
WRITE_FILE(json_file, json_encode(file_data))
+///Handles random hardcore point rewarding if it applies.
+/datum/controller/subsystem/ticker/proc/HandleRandomHardcoreScore(client/player_client)
+ if(!ishuman(player_client.mob))
+ return FALSE
+ var/mob/living/carbon/human/human_mob = player_client.mob
+ if(!human_mob.hardcore_survival_score) ///no score no glory
+ return FALSE
+
+ if(human_mob.mind && (human_mob.mind.special_role || length(human_mob.mind.antag_datums) > 0))
+ var/didthegamerwin = TRUE
+ for(var/a in human_mob.mind.antag_datums)
+ var/datum/antagonist/antag_datum = a
+ for(var/i in antag_datum.objectives)
+ var/datum/objective/objective_datum = i
+ if(!objective_datum.check_completion())
+ didthegamerwin = FALSE
+ if(!didthegamerwin)
+ return FALSE
+ player_client.give_award(/datum/award/score/hardcore_random, human_mob, round(human_mob.hardcore_survival_score))
+ else if(human_mob.onCentCom())
+ player_client.give_award(/datum/award/score/hardcore_random, human_mob, round(human_mob.hardcore_survival_score))
+
+
/datum/controller/subsystem/ticker/proc/declare_completion()
set waitfor = FALSE
to_chat(world, "
The round has ended.")
+ log_game("The round has ended.")
if(LAZYLEN(GLOB.round_end_notifiees))
world.TgsTargetedChatBroadcast("[GLOB.round_end_notifiees.Join(", ")] the round has ended.", FALSE)
@@ -186,6 +231,19 @@
C.RollCredits()
C.playtitlemusic(40)
CONFIG_SET(flag/suicide_allowed,TRUE) // EORG suicides allowed
+
+ var/speed_round = FALSE
+ if(world.time - SSticker.round_start_time <= 300 SECONDS)
+ speed_round = TRUE
+
+ for(var/client/C in GLOB.clients)
+ if(!C.credits)
+ C.RollCredits()
+ C.playtitlemusic(40)
+ if(speed_round)
+ C.give_award(/datum/award/achievement/misc/speed_round, C.mob)
+ HandleRandomHardcoreScore(C)
+
var/popcount = gather_roundend_feedback()
display_report(popcount)
@@ -204,7 +262,7 @@
var/survival_rate = GLOB.joined_player_list.len ? "[PERCENT(popcount[POPCOUNT_SURVIVORS]/GLOB.joined_player_list.len)]%" : "there's literally no player"
- send2irc("Server", "A round of [mode.name] just ended[mode_result == "undefined" ? "." : " with a [mode_result]."] Survival rate: [survival_rate]")
+ send2adminchat("Server", "A round of [mode.name] just ended[mode_result == "undefined" ? "." : " with a [mode_result]."] Survival rate: [survival_rate]")
if(length(CONFIG_GET(keyed_list/cross_server)))
send_news_report()
@@ -215,6 +273,11 @@
CHECK_TICK
+ // handle_hearts()
+ set_observer_default_invisibility(0, "The round is over! You are now visible to the living.")
+
+ CHECK_TICK
+
//These need update to actually reflect the real antagonists
//Print a list of antagonists to the server log
var/list/total_antagonists = list()
@@ -233,16 +296,13 @@
for(var/antag_name in total_antagonists)
var/list/L = total_antagonists[antag_name]
log_game("[antag_name]s :[L.Join(", ")].")
- set_observer_default_invisibility(0, "The round is over! You are now visible to the living.")
CHECK_TICK
SSdbcore.SetRoundEnd()
//Collects persistence features
- if(mode.station_was_nuked)
- SSpersistence.station_was_destroyed = TRUE
- if(!mode.allow_persistence_save)
- SSpersistence.station_persistence_save_disabled = TRUE
- SSpersistence.CollectData()
+ if(mode.allow_persistence_save)
+ SSpersistence.SaveTCGCards()
+ SSpersistence.CollectData()
//stop collecting feedback during grifftime
SSblackbox.Seal()
@@ -277,11 +337,15 @@
//Antagonists
parts += antag_report()
+ parts += hardcore_random_report()
+
CHECK_TICK
//Medals
parts += medal_report()
//Station Goals
parts += goal_report()
+ //Economy & Money
+ parts += market_report()
listclearnulls(parts)
@@ -324,9 +388,9 @@
parts += "[FOURSPACES]Nobody died this shift!"
if(istype(SSticker.mode, /datum/game_mode/dynamic))
var/datum/game_mode/dynamic/mode = SSticker.mode
- mode.update_playercounts()
- parts += "[FOURSPACES]Final threat level: [mode.threat_level]"
- parts += "[FOURSPACES]Final threat: [mode.threat]"
+ mode.update_playercounts() // ?
+ parts += "[FOURSPACES]Threat level: [mode.threat_level]"
+ parts += "[FOURSPACES]Threat left: [mode.threat]"
parts += "[FOURSPACES]Average threat: [mode.threat_average]"
parts += "[FOURSPACES]Executed rules:"
for(var/datum/dynamic_ruleset/rule in mode.executed_rules)
@@ -343,20 +407,47 @@
/client/proc/roundend_report_file()
return "data/roundend_reports/[ckey].html"
-/datum/controller/subsystem/ticker/proc/show_roundend_report(client/C, previous = FALSE)
+/**
+ * Log the round-end report as an HTML file
+ *
+ * Composits the roundend report, and saves it in two locations.
+ * The report is first saved along with the round's logs
+ * Then, the report is copied to a fixed directory specifically for
+ * housing the server's last roundend report. In this location,
+ * the file will be overwritten at the end of each shift.
+ */
+/datum/controller/subsystem/ticker/proc/log_roundend_report()
+ var/roundend_file = file("[GLOB.log_directory]/round_end_data.html")
+ var/list/parts = list()
+ parts += ""
+ parts += GLOB.survivor_report
+ parts += "
"
+ parts += GLOB.common_report
+ var/content = parts.Join()
+ //Log the rendered HTML in the round log directory
+ fdel(roundend_file)
+ WRITE_FILE(roundend_file, content)
+ //Place a copy in the root folder, to be overwritten each round.
+ roundend_file = file("data/server_last_roundend_report.html")
+ fdel(roundend_file)
+ WRITE_FILE(roundend_file, content)
+
+/datum/controller/subsystem/ticker/proc/show_roundend_report(client/C, report_type = null)
var/datum/browser/roundend_report = new(C, "roundend")
roundend_report.width = 800
roundend_report.height = 600
var/content
var/filename = C.roundend_report_file()
- if(!previous)
+ if(report_type == PERSONAL_LAST_ROUND) //Look at this player's last round
+ content = file2text(filename)
+ else if (report_type == SERVER_LAST_ROUND) //Look at the last round that this server has seen
+ content = file2text("data/server_last_roundend_report.html")
+ else //report_type is null, so make a new report based on the current round and show that to the player
var/list/report_parts = list(personal_report(C), GLOB.common_report)
content = report_parts.Join()
- remove_verb(C, /client/proc/show_previous_roundend_report)
fdel(filename)
text2file(content, filename)
- else
- content = file2text(filename)
+
roundend_report.set_content(content)
roundend_report.stylesheets = list()
roundend_report.add_stylesheet("roundend", 'html/browser/roundend.css')
@@ -393,8 +484,9 @@
/datum/controller/subsystem/ticker/proc/display_report(popcount)
GLOB.common_report = build_roundend_report()
GLOB.survivor_report = survivor_report(popcount)
+ log_roundend_report()
for(var/client/C in GLOB.clients)
- show_roundend_report(C, FALSE)
+ show_roundend_report(C)
give_show_report_button(C)
CHECK_TICK
@@ -412,12 +504,11 @@
if (aiPlayer.connected_robots.len)
var/borg_num = aiPlayer.connected_robots.len
- var/robolist = "
[aiPlayer.real_name]'s minions were: "
+ parts += "
[aiPlayer.real_name]'s minions were:"
for(var/mob/living/silicon/robot/robo in aiPlayer.connected_robots)
borg_num--
if(robo.mind)
- robolist += "[robo.name][robo.mind.hide_ckey ? "" : " (Played by: [robo.mind.key])"] [robo.stat == DEAD ? " (Deactivated)" : ""][borg_num ?", ":""]
"
- parts += "[robolist]"
+ parts += "[robo.name][robo.mind.hide_ckey ? "" : " (Played by: [robo.mind.key])"] [robo.stat == DEAD ? " (Deactivated)" : ""][borg_num ?", ":""]"
if(!borg_spacer)
borg_spacer = TRUE
@@ -444,6 +535,34 @@
parts += G.get_result()
return ""
+///Generate a report for how much money is on station, as well as the richest crewmember on the station.
+/datum/controller/subsystem/ticker/proc/market_report()
+ var/list/parts = list()
+ parts += ""
+ ///This is the richest account on station at roundend.
+ var/datum/bank_account/mr_moneybags
+ ///This is the station's total wealth at the end of the round.
+ var/station_vault = 0
+ ///How many players joined the round.
+ var/total_players = GLOB.joined_player_list.len
+ var/list/typecache_bank = typecacheof(list(/datum/bank_account/department, /datum/bank_account/remote))
+ for(var/i in SSeconomy.generated_accounts)
+ var/datum/bank_account/current_acc = SSeconomy.generated_accounts[i]
+ if(typecache_bank[current_acc.type])
+ continue
+ station_vault += current_acc.account_balance
+ if(!mr_moneybags || mr_moneybags.account_balance < current_acc.account_balance)
+ mr_moneybags = current_acc
+ parts += "There were [station_vault] credits collected by crew this shift.
"
+ if(total_players > 0)
+ parts += "An average of [station_vault/total_players] credits were collected.
"
+ // log_econ("Roundend credit total: [station_vault] credits. Average Credits: [station_vault/total_players]")
+ if(mr_moneybags)
+ parts += "The most affluent crew member at shift end was [mr_moneybags.account_holder] with [mr_moneybags.account_balance] cr!
"
+ else
+ parts += "Somehow, nobody made any money this shift! This'll result in some budget cuts..."
+ return parts
+
/datum/controller/subsystem/ticker/proc/medal_report()
if(GLOB.commendations.len)
var/list/parts = list()
@@ -453,16 +572,40 @@
return "[parts.Join("
")]
"
return ""
+///Generate a report for all players who made it out alive with a hardcore random character and prints their final score
+/datum/controller/subsystem/ticker/proc/hardcore_random_report()
+ . = list()
+ var/list/hardcores = list()
+ for(var/i in GLOB.player_list)
+ if(!ishuman(i))
+ continue
+ var/mob/living/carbon/human/human_player = i
+ if(!human_player.hardcore_survival_score || !human_player.onCentCom() || human_player.stat == DEAD) ///gotta escape nerd
+ continue
+ if(!human_player.mind)
+ continue
+ hardcores += human_player
+ if(!length(hardcores))
+ return
+ . += ""
+ . += "
"
+ for(var/mob/living/carbon/human/human_player in hardcores)
+ . += "- [printplayer(human_player.mind)] with a hardcore random score of [round(human_player.hardcore_survival_score)]
"
+ . += "
"
+
/datum/controller/subsystem/ticker/proc/antag_report()
var/list/result = list()
var/list/all_teams = list()
var/list/all_antagonists = list()
+ // for(var/datum/team/A in GLOB.antagonist_teams)
+ // all_teams |= A
+
for(var/datum/antagonist/A in GLOB.antagonists)
if(!A.owner)
continue
all_teams |= A.get_team()
- all_antagonists += A
+ all_antagonists |= A
for(var/datum/team/T in all_teams)
result += T.roundend_report()
@@ -515,9 +658,9 @@
/datum/action/report/Trigger()
if(owner && GLOB.common_report && SSticker.current_state == GAME_STATE_FINISHED)
- SSticker.show_roundend_report(owner.client, FALSE)
+ SSticker.show_roundend_report(owner.client)
-/datum/action/report/IsAvailable(silent = FALSE)
+/datum/action/report/IsAvailable()
return 1
/datum/action/report/Topic(href,href_list)
@@ -532,7 +675,9 @@
var/jobtext = ""
if(ply.assigned_role)
jobtext = " the [ply.assigned_role]"
- var/text = "[ply.hide_ckey ? "[ply.name][jobtext] " : "[ply.key] was [ply.name][jobtext] and "]"
+ var/text = (ply.hide_ckey ? \
+ "[ply.key] was [ply.name][jobtext] and" \
+ : "[ply.name][jobtext]")
if(ply.current)
if(ply.current.stat == DEAD)
text += " died"
@@ -589,11 +734,9 @@
var/list/sql_admins = list()
for(var/i in GLOB.protected_admins)
var/datum/admins/A = GLOB.protected_admins[i]
- var/sql_ckey = sanitizeSQL(A.target)
- var/sql_rank = sanitizeSQL(A.rank.name)
- sql_admins += list(list("ckey" = "'[sql_ckey]'", "rank" = "'[sql_rank]'"))
+ sql_admins += list(list("ckey" = A.target, "rank" = A.rank.name))
SSdbcore.MassInsert(format_table_name("admin"), sql_admins, duplicate_key = TRUE)
- var/datum/DBQuery/query_admin_rank_update = SSdbcore.NewQuery("UPDATE [format_table_name("player")] p INNER JOIN [format_table_name("admin")] a ON p.ckey = a.ckey SET p.lastadminrank = a.rank")
+ var/datum/db_query/query_admin_rank_update = SSdbcore.NewQuery("UPDATE [format_table_name("player")] p INNER JOIN [format_table_name("admin")] a ON p.ckey = a.ckey SET p.lastadminrank = a.rank")
query_admin_rank_update.Execute()
qdel(query_admin_rank_update)
@@ -626,15 +769,20 @@
flags += "can_edit_flags"
if(!flags.len)
continue
- var/sql_rank = sanitizeSQL(R.name)
var/flags_to_check = flags.Join(" != [R_EVERYTHING] AND ") + " != [R_EVERYTHING]"
- var/datum/DBQuery/query_check_everything_ranks = SSdbcore.NewQuery("SELECT flags, exclude_flags, can_edit_flags FROM [format_table_name("admin_ranks")] WHERE rank = '[sql_rank]' AND ([flags_to_check])")
+ var/datum/db_query/query_check_everything_ranks = SSdbcore.NewQuery(
+ "SELECT flags, exclude_flags, can_edit_flags FROM [format_table_name("admin_ranks")] WHERE rank = :rank AND ([flags_to_check])",
+ list("rank" = R.name)
+ )
if(!query_check_everything_ranks.Execute())
qdel(query_check_everything_ranks)
return
if(query_check_everything_ranks.NextRow()) //no row is returned if the rank already has the correct flag value
var/flags_to_update = flags.Join(" = [R_EVERYTHING], ") + " = [R_EVERYTHING]"
- var/datum/DBQuery/query_update_everything_ranks = SSdbcore.NewQuery("UPDATE [format_table_name("admin_ranks")] SET [flags_to_update] WHERE rank = '[sql_rank]'")
+ var/datum/db_query/query_update_everything_ranks = SSdbcore.NewQuery(
+ "UPDATE [format_table_name("admin_ranks")] SET [flags_to_update] WHERE rank = :rank",
+ list("rank" = R.name)
+ )
if(!query_update_everything_ranks.Execute())
qdel(query_update_everything_ranks)
return
diff --git a/code/__HELPERS/text.dm b/code/__HELPERS/text.dm
index 7c251edd88..dec44653af 100644
--- a/code/__HELPERS/text.dm
+++ b/code/__HELPERS/text.dm
@@ -13,10 +13,6 @@
* SQL sanitization
*/
-// Run all strings to be used in an SQL query through this proc first to properly escape out injection attempts.
-/proc/sanitizeSQL(t)
- return SSdbcore.Quote("[t]")
-
/proc/format_table_name(table as text)
return CONFIG_GET(string/feedback_tableprefix) + table
@@ -670,7 +666,7 @@ GLOBAL_LIST_INIT(binary, list("0","1"))
if(fexists(log))
oldjson = json_decode(file2text(log))
oldentries = oldjson["data"]
- if(!isemptylist(oldentries))
+ if(length(oldentries))
for(var/string in accepted)
for(var/old in oldentries)
if(string == old)
@@ -680,7 +676,7 @@ GLOBAL_LIST_INIT(binary, list("0","1"))
var/list/finalized = list()
finalized = accepted.Copy() + oldentries.Copy() //we keep old and unreferenced phrases near the bottom for culling
listclearnulls(finalized)
- if(!isemptylist(finalized) && length(finalized) > storemax)
+ if(length(finalized) > storemax)
finalized.Cut(storemax + 1)
fdel(log)
diff --git a/code/__HELPERS/type2type.dm b/code/__HELPERS/type2type.dm
index 0987e95dc9..87187a4c3d 100644
--- a/code/__HELPERS/type2type.dm
+++ b/code/__HELPERS/type2type.dm
@@ -78,21 +78,21 @@
//Turns a direction into text
/proc/dir2text(direction)
switch(direction)
- if(1)
+ if(NORTH)
return "north"
- if(2)
+ if(SOUTH)
return "south"
- if(4)
+ if(EAST)
return "east"
- if(8)
+ if(WEST)
return "west"
- if(5)
+ if(NORTHEAST)
return "northeast"
- if(6)
+ if(SOUTHEAST)
return "southeast"
- if(9)
+ if(NORTHWEST)
return "northwest"
- if(10)
+ if(SOUTHWEST)
return "southwest"
else
return
@@ -101,21 +101,21 @@
/proc/text2dir(direction)
switch(uppertext(direction))
if("NORTH")
- return 1
+ return NORTH
if("SOUTH")
- return 2
+ return SOUTH
if("EAST")
- return 4
+ return EAST
if("WEST")
- return 8
+ return WEST
if("NORTHEAST")
- return 5
+ return NORTHEAST
if("NORTHWEST")
- return 9
+ return NORTHWEST
if("SOUTHEAST")
- return 6
+ return SOUTHEAST
if("SOUTHWEST")
- return 10
+ return SOUTHWEST
else
return
diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm
index d95c89d19e..ee4b09d42c 100644
--- a/code/__HELPERS/unsorted.dm
+++ b/code/__HELPERS/unsorted.dm
@@ -263,7 +263,7 @@ Turf and target are separate in case you want to teleport some distance from a t
return .
//Returns a list of all items of interest with their name
-/proc/getpois(mobs_only=0,skip_mindless=0)
+/proc/getpois(mobs_only = FALSE, skip_mindless = FALSE, specify_dead_role = TRUE)
var/list/mobs = sortmobs()
var/list/namecounts = list()
var/list/pois = list()
@@ -277,7 +277,7 @@ Turf and target are separate in case you want to teleport some distance from a t
if(M.real_name && M.real_name != M.name)
name += " \[[M.real_name]\]"
- if(M.stat == DEAD)
+ if(M.stat == DEAD && specify_dead_role)
if(isobserver(M))
name += " \[ghost\]"
else
@@ -1070,7 +1070,7 @@ B --><-- A
return closest_atom
-proc/pick_closest_path(value, list/matches = get_fancy_list_of_atom_types())
+/proc/pick_closest_path(value, list/matches = get_fancy_list_of_atom_types())
if (value == FALSE) //nothing should be calling us with a number, so this is safe
value = input("Enter type to find (blank for all, cancel to cancel)", "Search for type") as null|text
if (isnull(value))
@@ -1202,7 +1202,7 @@ GLOBAL_REAL_VAR(list/stack_trace_storage)
GLOBAL_DATUM_INIT(dview_mob, /mob/dview, new)
//Version of view() which ignores darkness, because BYOND doesn't have it (I actually suggested it but it was tagged redundant, BUT HEARERS IS A T- /rant).
-/proc/dview(var/range = world.view, var/center, var/invis_flags = 0)
+/proc/dview(range = world.view, center, invis_flags = 0)
if(!center)
return
@@ -1222,6 +1222,10 @@ GLOBAL_DATUM_INIT(dview_mob, /mob/dview, new)
var/ready_to_die = FALSE
/mob/dview/Initialize() //Properly prevents this mob from gaining huds or joining any global lists
+ SHOULD_CALL_PARENT(FALSE)
+ if(flags_1 & INITIALIZED_1)
+ stack_trace("Warning: [src]([type]) initialized multiple times!")
+ flags_1 |= INITIALIZED_1
return INITIALIZE_HINT_NORMAL
/mob/dview/Destroy(force = FALSE)
@@ -1243,28 +1247,40 @@ GLOBAL_DATUM_INIT(dview_mob, /mob/dview, new)
#define FOR_DVIEW_END GLOB.dview_mob.loc = null
-//can a window be here, or is there a window blocking it?
-/proc/valid_window_location(turf/T, dir_to_check)
- if(!T)
+/**
+ * Checks whether the target turf is in a valid state to accept a directional window
+ * or other directional pseudo-dense object such as railings.
+ *
+ * Returns FALSE if the target turf cannot accept a directional window or railing.
+ * Returns TRUE otherwise.
+ *
+ * Arguments:
+ * * dest_turf - The destination turf to check for existing windows and railings
+ * * test_dir - The prospective dir of some atom you'd like to put on this turf.
+ * * is_fulltile - Whether the thing you're attempting to move to this turf takes up the entire tile or whether it supports multiple movable atoms on its tile.
+ */
+/proc/valid_window_location(turf/dest_turf, test_dir, is_fulltile = FALSE)
+ if(!dest_turf)
return FALSE
- for(var/obj/O in T)
- if(istype(O, /obj/machinery/door/window) && (O.dir == dir_to_check || dir_to_check == FULLTILE_WINDOW_DIR))
- return FALSE
- if(istype(O, /obj/structure/windoor_assembly))
- var/obj/structure/windoor_assembly/W = O
- if(W.ini_dir == dir_to_check || dir_to_check == FULLTILE_WINDOW_DIR)
+ for(var/obj/turf_content in dest_turf)
+ if(istype(turf_content, /obj/machinery/door/window))
+ if((turf_content.dir == test_dir) || is_fulltile)
return FALSE
- if(istype(O, /obj/structure/window))
- var/obj/structure/window/W = O
- if(W.ini_dir == dir_to_check || W.ini_dir == FULLTILE_WINDOW_DIR || dir_to_check == FULLTILE_WINDOW_DIR)
+ if(istype(turf_content, /obj/structure/windoor_assembly))
+ var/obj/structure/windoor_assembly/windoor_assembly = turf_content
+ if(windoor_assembly.dir == test_dir || is_fulltile)
return FALSE
- if(istype(O, /obj/structure/railing))
- var/obj/structure/railing/rail = O
- if(rail.ini_dir == dir_to_check || rail.ini_dir == FULLTILE_WINDOW_DIR || dir_to_check == FULLTILE_WINDOW_DIR)
+ if(istype(turf_content, /obj/structure/window))
+ var/obj/structure/window/window_structure = turf_content
+ if(window_structure.dir == test_dir || window_structure.fulltile || is_fulltile)
+ return FALSE
+ if(istype(turf_content, /obj/structure/railing))
+ var/obj/structure/railing/rail = turf_content
+ if(rail.dir == test_dir || is_fulltile)
return FALSE
return TRUE
-/proc/pass()
+/proc/pass(...)
return
/proc/get_mob_or_brainmob(occupant)
@@ -1575,33 +1591,6 @@ GLOBAL_DATUM_INIT(dview_mob, /mob/dview, new)
for(var/i in 1 to items_list[each_item])
new each_item(where_to)
-//sends a message to chat
-//config_setting should be one of the following
-//null - noop
-//empty string - use TgsTargetBroadcast with admin_only = FALSE
-//other string - use TgsChatBroadcast with the tag that matches config_setting, only works with TGS4, if using TGS3 the above method is used
-/proc/send2chat(message, config_setting)
- if(config_setting == null)
- return
-
- UNTIL(GLOB.tgs_initialized)
- if(!world.TgsAvailable())
- return
-
- var/datum/tgs_version/version = world.TgsVersion()
- if(config_setting == "" || version.suite == 3)
- world.TgsTargetedChatBroadcast(message, FALSE)
- return
-
- var/list/channels_to_use = list()
- for(var/I in world.TgsChatChannelInfo())
- var/datum/tgs_chat_channel/channel = I
- if(channel.tag == config_setting)
- channels_to_use += channel
-
- if(channels_to_use.len)
- world.TgsChatBroadcast()
-
//Checks to see if either the victim has a garlic necklace or garlic in their blood
/proc/blood_sucking_checks(var/mob/living/carbon/target, check_neck, check_blood)
//Bypass this if the target isnt carbon.
diff --git a/code/_compile_options.dm b/code/_compile_options.dm
index 0e0bd4ffaa..64b4129024 100644
--- a/code/_compile_options.dm
+++ b/code/_compile_options.dm
@@ -46,11 +46,11 @@
//Update this whenever you need to take advantage of more recent byond features
#define MIN_COMPILER_VERSION 513
-#define MIN_COMPILER_BUILD 1508
+#define MIN_COMPILER_BUILD 1514
#if DM_VERSION < MIN_COMPILER_VERSION || DM_BUILD < MIN_COMPILER_BUILD
//Don't forget to update this part
#error Your version of BYOND is too out-of-date to compile this project. Go to https://secure.byond.com/download and update.
-#error You need version 513.1508 or higher
+#error You need version 513.1514 or higher
#endif
//Additional code for the above flags.
@@ -62,10 +62,14 @@
#define FIND_REF_NO_CHECK_TICK
#endif
-#ifdef TRAVISBUILDING
+#ifdef CIBUILDING
#define UNIT_TESTS
#endif
-#ifdef TRAVISTESTING
+#ifdef CITESTING
#define TESTING
#endif
+
+// A reasonable number of maximum overlays an object needs
+// If you think you need more, rethink it
+#define MAX_ATOM_OVERLAYS 100
diff --git a/code/_globalvars/admin.dm b/code/_globalvars/admin.dm
new file mode 100644
index 0000000000..81037ff3dd
--- /dev/null
+++ b/code/_globalvars/admin.dm
@@ -0,0 +1,12 @@
+GLOBAL_LIST_EMPTY(stickybanadminexemptions) //stores a list of ckeys exempted from a stickyban (workaround for a bug)
+GLOBAL_LIST_EMPTY(stickybanadmintexts) //stores the entire stickyban list temporarily
+GLOBAL_VAR(stickbanadminexemptiontimerid) //stores the timerid of the callback that restores all stickybans after an admin joins
+
+// /proc/init_smites() //todo: add on the second wave
+// var/list/smites = list()
+// for (var/_smite_path in subtypesof(/datum/smite))
+// var/datum/smite/smite_path = _smite_path
+// smites[initial(smite_path.name)] = smite_path
+// return smites
+
+// GLOBAL_LIST_INIT_TYPED(smites, /datum/smite, init_smites())
diff --git a/code/_globalvars/lists/medals.dm b/code/_globalvars/lists/achievements.dm
old mode 100755
new mode 100644
similarity index 100%
rename from code/_globalvars/lists/medals.dm
rename to code/_globalvars/lists/achievements.dm
diff --git a/code/_globalvars/lists/flavor_misc.dm b/code/_globalvars/lists/flavor_misc.dm
index 7fad1690e6..936040432b 100644
--- a/code/_globalvars/lists/flavor_misc.dm
+++ b/code/_globalvars/lists/flavor_misc.dm
@@ -126,6 +126,7 @@ GLOBAL_LIST_INIT(ai_core_display_screens, list(
"Not Malf",
"Patriot",
"Pirate",
+ "Portrait",
"President",
"Rainbow",
"Clown",
@@ -158,6 +159,10 @@ GLOBAL_LIST_INIT(ai_core_display_screens, list(
else
if(input == "Random")
input = pick(GLOB.ai_core_display_screens - "Random")
+ if(input == "Portrait")
+ var/datum/portrait_picker/tgui = new(usr)//create the datum
+ tgui.ui_interact(usr)//datum has a tgui component, here we open the window
+ return "ai-portrait" //just take this until they decide
return "ai-[lowertext(input)]"
GLOBAL_LIST_INIT(security_depts_prefs, list(SEC_DEPT_RANDOM, SEC_DEPT_NONE, SEC_DEPT_ENGINEERING, SEC_DEPT_MEDICAL, SEC_DEPT_SCIENCE, SEC_DEPT_SUPPLY))
@@ -274,7 +279,7 @@ GLOBAL_LIST_INIT(redacted_strings, list("\[REDACTED\]", "\[CLASSIFIED\]", "\[ARC
GLOBAL_LIST_INIT(wisdoms, world.file2list("strings/wisdoms.txt"))
//LANGUAGE CHARACTER CUSTOMIZATION
-GLOBAL_LIST_INIT(speech_verbs, list("default","says","gibbers", "states", "chitters", "chimpers", "declares", "bellows", "buzzes" ,"beeps", "chirps", "clicks", "hisses" ,"poofs" , "puffs", "rattles", "mewls" ,"barks", "blorbles", "squeaks", "squawks", "flutters", "warbles"))
+GLOBAL_LIST_INIT(speech_verbs, list("default","says","gibbers", "states", "chitters", "chimpers", "declares", "bellows", "buzzes" ,"beeps", "chirps", "clicks", "hisses" ,"poofs" , "puffs", "rattles", "mewls" ,"barks", "blorbles", "squeaks", "squawks", "flutters", "warbles", "caws", "gekkers", "clucks"))
GLOBAL_LIST_INIT(roundstart_tongues, list("default","human tongue" = /obj/item/organ/tongue, "lizard tongue" = /obj/item/organ/tongue/lizard, "skeleton tongue" = /obj/item/organ/tongue/bone, "fly tongue" = /obj/item/organ/tongue/fly, "ipc tongue" = /obj/item/organ/tongue/robot/ipc, "xeno tongue" = /obj/item/organ/tongue/alien))
//SPECIES BODYPART LISTS
diff --git a/code/_globalvars/logging.dm b/code/_globalvars/logging.dm
index e9f98f836e..78d802dbbf 100644
--- a/code/_globalvars/logging.dm
+++ b/code/_globalvars/logging.dm
@@ -8,6 +8,8 @@ GLOBAL_VAR(world_qdel_log)
GLOBAL_PROTECT(world_qdel_log)
GLOBAL_VAR(world_attack_log)
GLOBAL_PROTECT(world_attack_log)
+// GLOBAL_VAR(world_econ_log)
+// GLOBAL_PROTECT(world_econ_log)
GLOBAL_VAR(world_href_log)
GLOBAL_PROTECT(world_href_log)
GLOBAL_VAR(round_id)
@@ -26,22 +28,28 @@ GLOBAL_VAR(query_debug_log)
GLOBAL_PROTECT(query_debug_log)
GLOBAL_VAR(world_job_debug_log)
GLOBAL_PROTECT(world_job_debug_log)
+// GLOBAL_VAR(world_mecha_log)
+// GLOBAL_PROTECT(world_mecha_log)
GLOBAL_VAR(world_virus_log)
GLOBAL_PROTECT(world_virus_log)
GLOBAL_VAR(world_asset_log)
GLOBAL_PROTECT(world_asset_log)
+// GLOBAL_VAR(world_cloning_log)
+// GLOBAL_PROTECT(world_cloning_log)
GLOBAL_VAR(world_map_error_log)
GLOBAL_PROTECT(world_map_error_log)
GLOBAL_VAR(world_paper_log)
GLOBAL_PROTECT(world_paper_log)
-GLOBAL_VAR(subsystem_log)
-GLOBAL_PROTECT(subsystem_log)
-GLOBAL_VAR(reagent_log)
-GLOBAL_PROTECT(reagent_log)
-GLOBAL_VAR(world_crafting_log)
-GLOBAL_PROTECT(world_crafting_log)
-GLOBAL_VAR(click_log)
-GLOBAL_PROTECT(click_log)
+GLOBAL_VAR(tgui_log)
+GLOBAL_PROTECT(tgui_log)
+GLOBAL_VAR(world_shuttle_log)
+GLOBAL_PROTECT(world_shuttle_log)
+
+GLOBAL_VAR(perf_log)
+GLOBAL_PROTECT(perf_log)
+
+// GLOBAL_VAR(demo_log)
+// GLOBAL_PROTECT(demo_log)
GLOBAL_LIST_EMPTY(bombers)
GLOBAL_PROTECT(bombers)
@@ -51,10 +59,7 @@ GLOBAL_LIST_EMPTY(lastsignalers) //keeps last 100 signals here in format: "[src]
GLOBAL_PROTECT(lastsignalers)
GLOBAL_LIST_EMPTY(lawchanges) //Stores who uploaded laws to which silicon-based lifeform, and what the law was
GLOBAL_PROTECT(lawchanges)
-GLOBAL_VAR(tgui_log)
-GLOBAL_PROTECT(tgui_log)
-GLOBAL_VAR(world_shuttle_log)
-GLOBAL_PROTECT(world_shuttle_log)
+
GLOBAL_LIST_EMPTY(combatlog)
GLOBAL_PROTECT(combatlog)
GLOBAL_LIST_EMPTY(IClog)
@@ -75,3 +80,13 @@ GLOBAL_PROTECT(picture_logging_id)
GLOBAL_VAR(picture_logging_prefix)
GLOBAL_PROTECT(picture_logging_prefix)
/////
+
+//// cit logging
+GLOBAL_VAR(subsystem_log)
+GLOBAL_PROTECT(subsystem_log)
+GLOBAL_VAR(reagent_log)
+GLOBAL_PROTECT(reagent_log)
+GLOBAL_VAR(world_crafting_log)
+GLOBAL_PROTECT(world_crafting_log)
+GLOBAL_VAR(click_log)
+GLOBAL_PROTECT(click_log)
diff --git a/code/_globalvars/traits.dm b/code/_globalvars/traits.dm
index 4e593ba904..ac6ea4e25c 100644
--- a/code/_globalvars/traits.dm
+++ b/code/_globalvars/traits.dm
@@ -130,7 +130,11 @@ GLOBAL_LIST_INIT(traits_by_type, list(
),
/obj/item = list(
"TRAIT_NODROP" = TRAIT_NODROP,
- "TRAIT_NO_TELEPORT" = TRAIT_NO_TELEPORT
+ "TRAIT_NO_TELEPORT" = TRAIT_NO_TELEPORT,
+ "TRAIT_SPOOKY_THROW" = TRAIT_SPOOKY_THROW
+ ),
+ /datum/mind = list(
+ "TRAIT_CLOWN_MENTALITY" = TRAIT_CLOWN_MENTALITY
)
))
diff --git a/code/_onclick/ai.dm b/code/_onclick/ai.dm
index 59b78577af..f153b5965c 100644
--- a/code/_onclick/ai.dm
+++ b/code/_onclick/ai.dm
@@ -48,7 +48,7 @@
to_chat(src, "You're experiencing a bug. Reconnect immediately to fix it. Admins have been notified.")
if(REALTIMEOFDAY >= chnotify + 9000)
chnotify = REALTIMEOFDAY
- send2irc_adminless_only("NOCHEAT", message)
+ send2tgs_adminless_only("NOCHEAT", message)
return
var/list/modifiers = params2list(params)
@@ -113,7 +113,7 @@
A.AICtrlClick(src)
/mob/living/silicon/ai/AltClickOn(var/atom/A)
A.AIAltClick(src)
-
+
/*
The following criminally helpful code is just the previous code cleaned up;
diff --git a/code/_onclick/hud/_defines.dm b/code/_onclick/hud/_defines.dm
index 467c67e0c3..a1b7a74384 100644
--- a/code/_onclick/hud/_defines.dm
+++ b/code/_onclick/hud/_defines.dm
@@ -17,21 +17,6 @@
Therefore, the top right corner (except during admin shenanigans) is at "15,15"
*/
-//Lower left, persistent menu
-#define ui_inventory "WEST:6,SOUTH:5"
-
-//Middle left indicators
-#define ui_lingchemdisplay "WEST,CENTER-1:15"
-#define ui_lingstingdisplay "WEST:6,CENTER-3:11"
-
-#define ui_devilsouldisplay "WEST:6,CENTER-1:15"
-
-//Lower center, persistent menu
-#define ui_sstore1 "CENTER-5:10,SOUTH:5"
-#define ui_id "CENTER-4:12,SOUTH:5"
-#define ui_belt "CENTER-3:14,SOUTH:5"
-#define ui_back "CENTER-2:14,SOUTH:5"
-
/proc/ui_hand_position(i) //values based on old hand ui positions (CENTER:-/+16,SOUTH:5)
var/x_off = -(!(i % 2))
var/y_off = round((i-1) / 2)
@@ -46,35 +31,23 @@
var/y_off = round((M.held_items.len-1) / 2)
return "CENTER+[x_off]:16,SOUTH+[y_off+1]:5"
+//Lower left, persistent menu
+#define ui_inventory "WEST:6,SOUTH:5"
+
+//Middle left indicators
+#define ui_lingchemdisplay "WEST,CENTER-1:15"
+#define ui_lingstingdisplay "WEST:6,CENTER-3:11"
+
+#define ui_devilsouldisplay "WEST:6,CENTER-1:15"
+
+//Lower center, persistent menu
+#define ui_sstore1 "CENTER-5:10,SOUTH:5"
+#define ui_id "CENTER-4:12,SOUTH:5"
+#define ui_belt "CENTER-3:14,SOUTH:5"
+#define ui_back "CENTER-2:14,SOUTH:5"
#define ui_storage1 "CENTER+1:18,SOUTH:5"
#define ui_storage2 "CENTER+2:20,SOUTH:5"
-#define ui_borg_sensor "CENTER-3:15, SOUTH:5" //borgs
-#define ui_borg_lamp "CENTER-4:15, SOUTH:5" //borgs
-#define ui_borg_thrusters "CENTER-5:15, SOUTH:5" //borgs
-#define ui_inv1 "CENTER-2:16,SOUTH:5" //borgs
-#define ui_inv2 "CENTER-1 :16,SOUTH:5" //borgs
-#define ui_inv3 "CENTER :16,SOUTH:5" //borgs
-#define ui_borg_module "CENTER+1:16,SOUTH:5" //borgs
-#define ui_borg_store "CENTER+2:16,SOUTH:5" //borgs
-#define ui_borg_camera "CENTER+3:21,SOUTH:5" //borgs
-#define ui_borg_album "CENTER+4:21,SOUTH:5" //borgs
-#define ui_borg_language_menu "EAST-1:27,SOUTH+2:8" //borgs
-
-#define ui_monkey_head "CENTER-5:13,SOUTH:5" //monkey
-#define ui_monkey_mask "CENTER-4:14,SOUTH:5" //monkey
-#define ui_monkey_neck "CENTER-3:15,SOUTH:5" //monkey
-#define ui_monkey_back "CENTER-2:16,SOUTH:5" //monkey
-
-//#define ui_alien_storage_l "CENTER-2:14,SOUTH:5"//alien
-#define ui_alien_storage_r "CENTER+1:18,SOUTH:5"//alien
-#define ui_alien_language_menu "EAST-3:26,SOUTH:5" //alien
-
-#define ui_drone_drop "CENTER+1:18,SOUTH:5" //maintenance drones
-#define ui_drone_pull "CENTER+2:2,SOUTH:5" //maintenance drones
-#define ui_drone_storage "CENTER-2:14,SOUTH:5" //maintenance drones
-#define ui_drone_head "CENTER-3:14,SOUTH:5" //maintenance drones
-
//Lower right, persistent menu
#define ui_drop_throw "EAST-1:28,SOUTH+1:7"
#define ui_pull_resist "EAST-2:26,SOUTH+1:7"
@@ -88,11 +61,6 @@
#define ui_language_menu "EAST-5:4,SOUTH:21"//CIT CHANGE - ditto
#define ui_voremode "EAST-5:20,SOUTH:5"
-#define ui_borg_pull "EAST-2:26,SOUTH+1:7"
-#define ui_borg_radio "EAST-1:28,SOUTH+1:7"
-#define ui_borg_intents "EAST-2:26,SOUTH:5"
-
-
//Upper-middle right (alerts)
#define ui_alert1 "EAST-1:28,CENTER+5:27"
#define ui_alert2 "EAST-1:28,CENTER+4:25"
@@ -100,31 +68,70 @@
#define ui_alert4 "EAST-1:28,CENTER+2:21"
#define ui_alert5 "EAST-1:28,CENTER+1:19"
-
//Middle right (status indicators)
#define ui_healthdoll "EAST-1:28,CENTER-2:13"
#define ui_health "EAST-1:28,CENTER-1:15"
#define ui_internal "EAST-1:28,CENTER+1:19"//CIT CHANGE - moves internal icon up a little bit to accommodate for the stamina meter
#define ui_mood "EAST-1:28,CENTER-3:10"
+// #define ui_spacesuit "EAST-1:28,CENTER-4:10"
-//living
+//Pop-up inventory
+#define ui_shoes "WEST+1:8,SOUTH:5"
+#define ui_iclothing "WEST:6,SOUTH+1:7"
+#define ui_oclothing "WEST+1:8,SOUTH+1:7"
+#define ui_gloves "WEST+2:10,SOUTH+1:7"
+#define ui_glasses "WEST:6,SOUTH+3:11"
+#define ui_mask "WEST+1:8,SOUTH+2:9"
+#define ui_ears "WEST+2:10,SOUTH+2:9"
+#define ui_neck "WEST:6,SOUTH+2:9"
+#define ui_head "WEST+1:8,SOUTH+3:11"
+
+//Generic living
#define ui_living_pull "EAST-1:28,CENTER-2:15"
#define ui_living_health "EAST-1:28,CENTER:15"
-//borgs
-#define ui_borg_health "EAST-1:28,CENTER-1:15" //borgs have the health display where humans have the pressure damage indicator.
+//Monkeys
+#define ui_monkey_head "CENTER-5:13,SOUTH:5"
+#define ui_monkey_mask "CENTER-4:14,SOUTH:5"
+#define ui_monkey_neck "CENTER-3:15,SOUTH:5"
+#define ui_monkey_back "CENTER-2:16,SOUTH:5"
-//aliens
-#define ui_alien_health "EAST,CENTER-1:15" //aliens have the health display where humans have the pressure damage indicator.
+//Drones
+#define ui_drone_drop "CENTER+1:18,SOUTH:5"
+#define ui_drone_pull "CENTER+2:2,SOUTH:5"
+#define ui_drone_storage "CENTER-2:14,SOUTH:5"
+#define ui_drone_head "CENTER-3:14,SOUTH:5"
+
+//Cyborgs
+#define ui_borg_health "EAST-1:28,CENTER-1:15"
+#define ui_borg_pull "EAST-2:26,SOUTH+1:7"
+#define ui_borg_radio "EAST-1:28,SOUTH+1:7"
+#define ui_borg_intents "EAST-2:26,SOUTH:5"
+#define ui_borg_lamp "CENTER-3:16, SOUTH:5"
+#define ui_borg_tablet "CENTER-4:16, SOUTH:5"
+#define ui_inv1 "CENTER-2:16,SOUTH:5"
+#define ui_inv2 "CENTER-1 :16,SOUTH:5"
+#define ui_inv3 "CENTER :16,SOUTH:5"
+#define ui_borg_module "CENTER+1:16,SOUTH:5"
+#define ui_borg_store "CENTER+2:16,SOUTH:5"
+#define ui_borg_camera "CENTER+3:21,SOUTH:5"
+#define ui_borg_alerts "CENTER+4:21,SOUTH:5"
+#define ui_borg_language_menu "CENTER+4:21,SOUTH+1:5"
+#define ui_borg_sensor "CENTER-6:16, SOUTH:5" //LEGACY
+#define ui_borg_thrusters "CENTER-5:16, SOUTH:5" //LEGACY
+
+//Aliens
+#define ui_alien_health "EAST,CENTER-1:15"
#define ui_alienplasmadisplay "EAST,CENTER-2:15"
#define ui_alien_queen_finder "EAST,CENTER-3:15"
+#define ui_alien_storage_r "CENTER+1:18,SOUTH:5"
+#define ui_alien_language_menu "EAST-3:26,SOUTH:5"
-//constructs
+//Constructs
#define ui_construct_pull "EAST,CENTER-2:15"
-#define ui_construct_health "EAST,CENTER:15" //same as borgs and humans
+#define ui_construct_health "EAST,CENTER:15"
// AI
-
#define ui_ai_core "SOUTH:6,WEST"
#define ui_ai_camera_list "SOUTH:6,WEST+1"
#define ui_ai_track_with_camera "SOUTH:6,WEST+2"
@@ -143,26 +150,32 @@
#define ui_ai_multicam "SOUTH+1:6,WEST+13"
#define ui_ai_add_multicam "SOUTH+1:6,WEST+14"
-//Pop-up inventory
-#define ui_shoes "WEST+1:8,SOUTH:5"
-#define ui_iclothing "WEST:6,SOUTH+1:7"
-#define ui_oclothing "WEST+1:8,SOUTH+1:7"
-#define ui_gloves "WEST+2:10,SOUTH+1:7"
-
-#define ui_glasses "WEST:6,SOUTH+3:11"
-#define ui_mask "WEST+1:8,SOUTH+2:9"
-#define ui_ears "WEST+2:10,SOUTH+2:9"
-#define ui_neck "WEST:6,SOUTH+2:9"
-#define ui_head "WEST+1:8,SOUTH+3:11"
+// pAI
+// #define ui_pai_software "SOUTH:6,WEST"
+// #define ui_pai_shell "SOUTH:6,WEST+1"
+// #define ui_pai_chassis "SOUTH:6,WEST+2"
+// #define ui_pai_rest "SOUTH:6,WEST+3"
+// #define ui_pai_light "SOUTH:6,WEST+4"
+// #define ui_pai_newscaster "SOUTH:6,WEST+5"
+// #define ui_pai_host_monitor "SOUTH:6,WEST+6"
+// #define ui_pai_crew_manifest "SOUTH:6,WEST+7"
+// #define ui_pai_state_laws "SOUTH:6,WEST+8"
+// #define ui_pai_pda_send "SOUTH:6,WEST+9"
+// #define ui_pai_pda_log "SOUTH:6,WEST+10"
+// #define ui_pai_take_picture "SOUTH:6,WEST+12"
+// #define ui_pai_view_images "SOUTH:6,WEST+13"
//Ghosts
+#define ui_ghost_jumptomob "SOUTH:6,CENTER-3:24"
+#define ui_ghost_orbit "SOUTH:6,CENTER-2:24"
+#define ui_ghost_reenter_corpse "SOUTH:6,CENTER-1:24"
+#define ui_ghost_teleport "SOUTH:6,CENTER:24"
+#define ui_ghost_pai "SOUTH: 6, CENTER+1:24"
+#define ui_ghost_mafia "SOUTH: 6, CENTER+2:24"
+#define ui_ghost_spawners "SOUTH: 6, CENTER+1:24" // LEGACY. SAME LOC AS PAI
-#define ui_ghost_jumptomob "SOUTH:6,CENTER-2:24"
-#define ui_ghost_orbit "SOUTH:6,CENTER-1:24"
-#define ui_ghost_reenter_corpse "SOUTH:6,CENTER:24"
-#define ui_ghost_teleport "SOUTH:6,CENTER+1:24"
-#define ui_ghost_spawners "SOUTH: 6, CENTER+2:24"
+// #define ui_wanted_lvl "NORTH,11"
//UI position overrides for 1:1 screen layout. (default is 7:5)
diff --git a/code/_onclick/hud/alert.dm b/code/_onclick/hud/alert.dm
index 5bd9632355..81d8593d4e 100644
--- a/code/_onclick/hud/alert.dm
+++ b/code/_onclick/hud/alert.dm
@@ -220,7 +220,7 @@
/obj/screen/alert/shiver
name = "Shivering"
- desc = "You're shivering! Get somewhere warmer and take off any insulating clothing like a space suit."
+ desc = "You're shivering! Get somewhere warmer and take off any insulating clothing like a space suit."
/obj/screen/alert/lowpressure
name = "Low Pressure"
@@ -306,6 +306,39 @@ or shoot a gun to move around via Newton's 3rd Law of Motion."
if(CHECK_MOBILITY(L, MOBILITY_MOVE))
return L.resist_fire() //I just want to start a flame in your hearrrrrrtttttt.
+/obj/screen/alert/give // information set when the give alert is made
+ icon_state = "default"
+ var/mob/living/carbon/giver
+ var/obj/item/receiving
+
+/**
+ * Handles assigning most of the variables for the alert that pops up when an item is offered
+ *
+ * Handles setting the name, description and icon of the alert and tracking the person giving
+ * and the item being offered, also registers a signal that removes the alert from anyone who moves away from the giver
+ * Arguments:
+ * * taker - The person receiving the alert
+ * * giver - The person giving the alert and item
+ * * receiving - The item being given by the giver
+ */
+/obj/screen/alert/give/proc/setup(mob/living/carbon/taker, mob/living/carbon/giver, obj/item/receiving)
+ name = "[giver] is offering [receiving]"
+ desc = "[giver] is offering [receiving]. Click this alert to take it."
+ icon_state = "template"
+ cut_overlays()
+ add_overlay(receiving)
+ src.receiving = receiving
+ src.giver = giver
+ RegisterSignal(taker, COMSIG_MOVABLE_MOVED, .proc/removeAlert)
+
+/obj/screen/alert/give/proc/removeAlert()
+ to_chat(usr, "You moved out of range of [giver]!")
+ usr.clear_alert("[giver]")
+
+/obj/screen/alert/give/Click(location, control, params)
+ . = ..()
+ var/mob/living/carbon/C = usr
+ C.take(giver, receiving)
//ALIENS
diff --git a/code/_onclick/hud/fullscreen.dm b/code/_onclick/hud/fullscreen.dm
index d82f3e7cf5..076c949f8e 100644
--- a/code/_onclick/hud/fullscreen.dm
+++ b/code/_onclick/hud/fullscreen.dm
@@ -149,6 +149,15 @@
/obj/screen/fullscreen/color_vision/blue
color = "#0000ff"
+/obj/screen/fullscreen/cinematic_backdrop
+ icon = 'icons/mob/screen_gen.dmi'
+ screen_loc = "WEST,SOUTH to EAST,NORTH"
+ icon_state = "flash"
+ plane = SPLASHSCREEN_PLANE
+ layer = SPLASHSCREEN_LAYER - 1
+ color = "#000000"
+ show_when_dead = TRUE
+
/obj/screen/fullscreen/lighting_backdrop
icon = 'icons/mob/screen_gen.dmi'
icon_state = "flash"
diff --git a/code/_onclick/hud/human.dm b/code/_onclick/hud/human.dm
index 04141becf2..841a3e8303 100644
--- a/code/_onclick/hud/human.dm
+++ b/code/_onclick/hud/human.dm
@@ -118,27 +118,7 @@
action_intent.hud = src
static_inventory += action_intent
- using = new /obj/screen/mov_intent
- using.icon = tg_ui_icon_to_cit_ui(ui_style) // CIT CHANGE - overrides mov intent icon
- using.icon_state = (mymob.m_intent == MOVE_INTENT_RUN ? "running" : "walking")
- using.screen_loc = ui_movi
- using.hud = src
- static_inventory += using
-
- //CITADEL CHANGES - sprint button
- using = new /obj/screen/sprintbutton
- using.icon = tg_ui_icon_to_cit_ui(ui_style)
- using.icon_state = ((owner.combat_flags & COMBAT_FLAG_SPRINT_ACTIVE) ? "act_sprint_on" : "act_sprint")
- using.screen_loc = ui_movi
- using.hud = src
- static_inventory += using
- //END OF CITADEL CHANGES
-
- //same as above but buffer.
- sprint_buffer = new /obj/screen/sprint_buffer
- sprint_buffer.screen_loc = ui_sprintbufferloc
- sprint_buffer.hud = src
- static_inventory += sprint_buffer
+ assert_move_intent_ui(owner, TRUE)
// clickdelay
clickdelay = new
@@ -393,6 +373,51 @@
update_locked_slots()
+/datum/hud/human/proc/assert_move_intent_ui(mob/living/carbon/human/owner = mymob, on_new = FALSE)
+ var/obj/screen/using
+ // delete old ones
+ var/list/obj/screen/victims = list()
+ victims += locate(/obj/screen/mov_intent) in static_inventory
+ victims += locate(/obj/screen/sprintbutton) in static_inventory
+ victims += locate(/obj/screen/sprint_buffer) in static_inventory
+ if(victims)
+ static_inventory -= victims
+ if(mymob?.client)
+ mymob.client.screen -= victims
+ QDEL_LIST(victims)
+
+ // make new ones
+ // walk/run
+ using = new /obj/screen/mov_intent
+ using.icon = tg_ui_icon_to_cit_ui(ui_style) // CIT CHANGE - overrides mov intent icon
+ using.screen_loc = ui_movi
+ using.hud = src
+ using.update_icon()
+ static_inventory += using
+ if(!on_new)
+ owner?.client?.screen += using
+
+ if(!CONFIG_GET(flag/sprint_enabled))
+ return
+
+ // sprint button
+ using = new /obj/screen/sprintbutton
+ using.icon = tg_ui_icon_to_cit_ui(ui_style)
+ using.icon_state = ((owner.combat_flags & COMBAT_FLAG_SPRINT_ACTIVE) ? "act_sprint_on" : "act_sprint")
+ using.screen_loc = ui_movi
+ using.hud = src
+ static_inventory += using
+ if(!on_new)
+ owner?.client?.screen += using
+
+ // same as above but buffer.
+ sprint_buffer = new /obj/screen/sprint_buffer
+ sprint_buffer.screen_loc = ui_sprintbufferloc
+ sprint_buffer.hud = src
+ static_inventory += sprint_buffer
+ if(!on_new)
+ owner?.client?.screen += using
+
/datum/hud/human/update_locked_slots()
if(!mymob)
return
diff --git a/code/_onclick/hud/parallax.dm b/code/_onclick/hud/parallax.dm
index 9050bcb5f0..5d48f430d7 100755
--- a/code/_onclick/hud/parallax.dm
+++ b/code/_onclick/hud/parallax.dm
@@ -10,6 +10,8 @@
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)
+ 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 = C.parallax_layers_cached.Copy()
@@ -52,12 +54,12 @@
switch(C.prefs.parallax)
if (PARALLAX_INSANE)
C.parallax_throttle = FALSE
- C.parallax_layers_max = 4
+ C.parallax_layers_max = 5
return TRUE
if (PARALLAX_MED)
C.parallax_throttle = PARALLAX_DELAY_MED
- C.parallax_layers_max = 2
+ C.parallax_layers_max = 3
return TRUE
if (PARALLAX_LOW)
@@ -68,8 +70,9 @@
if (PARALLAX_DISABLE)
return FALSE
+ //This is high parallax.
C.parallax_throttle = PARALLAX_DELAY_DEFAULT
- C.parallax_layers_max = 3
+ C.parallax_layers_max = 4
return TRUE
/datum/hud/proc/update_parallax_pref(mob/viewmob)
@@ -219,15 +222,14 @@
L.screen_loc = "CENTER-7:[round(L.offset_x,1)],CENTER-7:[round(L.offset_y,1)]"
/atom/movable/proc/update_parallax_contents()
- set waitfor = FALSE
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))
+ if(M?.client && M.hud_used && length(M.client.parallax_layers))
M.hud_used.update_parallax()
/mob/proc/update_parallax_teleport() //used for arrivals shuttle
- if(client && client.eye && hud_used && length(client.parallax_layers))
+ if(client?.eye && hud_used && length(client.parallax_layers))
var/area/areaobj = get_area(client.eye)
hud_used.set_parallax_movedir(areaobj.parallax_movedir, TRUE)
@@ -287,6 +289,21 @@
speed = 1.4
layer = 3
+/obj/screen/parallax_layer/random
+ blend_mode = BLEND_OVERLAY
+ speed = 3
+ layer = 3
+
+/obj/screen/parallax_layer/random/space_gas
+ icon_state = "space_gas"
+
+/obj/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
+ icon_state = "asteroids"
+
/obj/screen/parallax_layer/planet
icon_state = "planet"
blend_mode = BLEND_OVERLAY
@@ -295,11 +312,11 @@
layer = 30
/obj/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
+ var/client/C = M.client
+ var/turf/posobj = get_turf(C.eye)
+ if(!posobj)
+ return
+ invisibility = is_station_level(posobj.z) ? 0 : INVISIBILITY_ABSTRACT
/obj/screen/parallax_layer/planet/update_o()
- return //Shit wont move
+ return //Shit won't move
diff --git a/code/_onclick/hud/plane_master.dm b/code/_onclick/hud/plane_master.dm
index 7a8b0a1121..4c736c9a2b 100644
--- a/code/_onclick/hud/plane_master.dm
+++ b/code/_onclick/hud/plane_master.dm
@@ -28,8 +28,6 @@
. = ..()
filters += filter(type="alpha", render_source=FIELD_OF_VISION_RENDER_TARGET, flags=MASK_INVERSE)
-/obj/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)
@@ -40,9 +38,6 @@
/obj/screen/plane_master/proc/shadow(_size, _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()
-
///Contains just the floor
/obj/screen/plane_master/floor
name = "floor plane master"
@@ -93,13 +88,6 @@
else
remove_filter("ambient_occlusion")
-//Reserved to chat messages, so they are still displayed above the field of vision masking.
-/obj/screen/plane_master/chat_messages
- name = "chat messages plane master"
- plane = CHAT_PLANE
- appearance_flags = PLANE_MASTER
- mouse_opacity = MOUSE_OPACITY_TRANSPARENT
-
///Contains all shadow cone masks, whose image overrides are displayed only to their respective owners.
/obj/screen/plane_master/field_of_vision
name = "field of vision mask plane master"
@@ -135,10 +123,14 @@
blend_mode = BLEND_MULTIPLY
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+/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)
+
/obj/screen/plane_master/lighting/Initialize()
. = ..()
- filters += filter(type="alpha", render_source=EMISSIVE_RENDER_TARGET, flags=MASK_INVERSE)
- filters += filter(type="alpha", render_source=EMISSIVE_UNBLOCKABLE_RENDER_TARGET, flags=MASK_INVERSE)
+ filters += filter(type="alpha", render_source = EMISSIVE_RENDER_TARGET, flags = MASK_INVERSE)
+ filters += filter(type="alpha", render_source = EMISSIVE_UNBLOCKABLE_RENDER_TARGET, flags = MASK_INVERSE)
/**
* Things placed on this mask the lighting plane. Doesn't render directly.
@@ -186,7 +178,6 @@
render_target = EMISSIVE_BLOCKER_RENDER_TARGET
///Contains space parallax
-
/obj/screen/plane_master/parallax
name = "parallax plane master"
plane = PLANE_SPACE_PARALLAX
@@ -197,12 +188,16 @@
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)
-
/obj/screen/plane_master/camera_static
name = "camera static plane master"
plane = CAMERA_STATIC_PLANE
appearance_flags = PLANE_MASTER
blend_mode = BLEND_OVERLAY
+
+
+//Reserved to chat messages, so they are still displayed above the field of vision masking.
+/obj/screen/plane_master/chat_messages
+ name = "runechat plane master"
+ plane = CHAT_PLANE
+ appearance_flags = PLANE_MASTER
+ blend_mode = BLEND_OVERLAY
diff --git a/code/_onclick/hud/robot.dm b/code/_onclick/hud/robot.dm
index 9859e7acdf..33a9cec80c 100644
--- a/code/_onclick/hud/robot.dm
+++ b/code/_onclick/hud/robot.dm
@@ -68,57 +68,17 @@
var/mob/living/silicon/robot/R = usr
R.uneq_active()
-/obj/screen/robot/lamp
- name = "headlamp"
- icon_state = "lamp0"
-
-/obj/screen/robot/lamp/Click()
- if(..())
- return
- var/mob/living/silicon/robot/R = usr
- R.control_headlamp()
-
-/obj/screen/robot/thrusters
- name = "ion thrusters"
- icon_state = "ionpulse0"
-
-/obj/screen/robot/thrusters/Click()
- if(..())
- return
- var/mob/living/silicon/robot/R = usr
- R.toggle_ionpulse()
-
-/obj/screen/robot/sensors
- name = "Sensor Augmentation"
- icon_state = "cyborg_sensor"
-
-/obj/screen/robot/sensors/Click()
- if(..())
- return
- var/mob/living/silicon/S = usr
- S.toggle_sensors()
-
-/obj/screen/robot/language_menu
- name = "silicon language selection"
- icon_state = "talk_wheel"
-
-/obj/screen/robot/language_menu/Click()
- if(..())
- return
- var/mob/living/silicon/S = usr
- S.open_language_menu(usr)
-
/datum/hud/robot
ui_style = 'icons/mob/screen_cyborg.dmi'
/datum/hud/robot/New(mob/owner)
..()
- var/mob/living/silicon/robot/mymobR = mymob
+ // i, Robit
+ var/mob/living/silicon/robot/robit = mymob
var/obj/screen/using
- using = new/obj/screen/robot/language_menu
+ using = new/obj/screen/language_menu
using.screen_loc = ui_borg_language_menu
- using.hud = src
static_inventory += using
//Radio
@@ -128,56 +88,72 @@
static_inventory += using
//Module select
- using = new /obj/screen/robot/module1()
- using.screen_loc = ui_inv1
- using.hud = src
- static_inventory += using
- mymobR.inv1 = using
+ if(!robit.inv1)
+ robit.inv1 = new /obj/screen/robot/module1()
- using = new /obj/screen/robot/module2()
- using.screen_loc = ui_inv2
- using.hud = src
- static_inventory += using
- mymobR.inv2 = using
+ robit.inv1.screen_loc = ui_inv1
+ robit.inv1.hud = src
+ static_inventory += robit.inv1
- using = new /obj/screen/robot/module3()
- using.screen_loc = ui_inv3
- using.hud = src
- static_inventory += using
- mymobR.inv3 = using
+ if(!robit.inv2)
+ robit.inv2 = new /obj/screen/robot/module2()
+
+ robit.inv2.screen_loc = ui_inv2
+ robit.inv2.hud = src
+ static_inventory += robit.inv2
+
+ if(!robit.inv3)
+ robit.inv3 = new /obj/screen/robot/module3()
+
+ robit.inv3.screen_loc = ui_inv3
+ robit.inv3.hud = src
+ static_inventory += robit.inv3
//End of module select
+ using = new /obj/screen/robot/lamp()
+ using.screen_loc = ui_borg_lamp
+ using.hud = src
+ static_inventory += using
+ robit.lampButton = using
+ var/obj/screen/robot/lamp/lampscreen = using
+ lampscreen.robot = robit
+
//Photography stuff
using = new /obj/screen/ai/image_take()
using.screen_loc = ui_borg_camera
using.hud = src
static_inventory += using
- using = new /obj/screen/ai/image_view()
- using.screen_loc = ui_borg_album
- using.hud = src
- static_inventory += using
-
//Sec/Med HUDs
using = new /obj/screen/robot/sensors()
using.screen_loc = ui_borg_sensor
using.hud = src
static_inventory += using
-//Headlamp control
- using = new /obj/screen/robot/lamp()
- using.screen_loc = ui_borg_lamp
+//Borg Integrated Tablet
+ using = new /obj/screen/robot/modPC()
+ using.screen_loc = ui_borg_tablet
+ using.hud = src
+ static_inventory += using
+ robit.interfaceButton = using
+ if(robit.modularInterface)
+ using.vis_contents += robit.modularInterface
+ var/obj/screen/robot/modPC/tabletbutton = using
+ tabletbutton.robot = robit
+
+//Alerts
+ using = new /obj/screen/robot/alerts()
+ using.screen_loc = ui_borg_alerts
using.hud = src
static_inventory += using
- mymobR.lamp_button = using
//Thrusters
using = new /obj/screen/robot/thrusters()
using.screen_loc = ui_borg_thrusters
using.hud = src
static_inventory += using
- mymobR.thruster_button = using
+ robit.thruster_button = using
//Intent
action_intent = new /obj/screen/act_intent/robot()
@@ -191,20 +167,21 @@
infodisplay += healths
//Installed Module
- mymobR.hands = new /obj/screen/robot/module()
- mymobR.hands.screen_loc = ui_borg_module
- static_inventory += mymobR.hands
+ robit.hands = new /obj/screen/robot/module()
+ robit.hands.screen_loc = ui_borg_module
+ robit.hands.hud = src
+ static_inventory += robit.hands
//Store
module_store_icon = new /obj/screen/robot/store()
- module_store_icon.hud = src
module_store_icon.screen_loc = ui_borg_store
+ module_store_icon.hud = src
pull_icon = new /obj/screen/pull()
pull_icon.icon = 'icons/mob/screen_cyborg.dmi'
+ pull_icon.screen_loc = ui_borg_pull
pull_icon.hud = src
pull_icon.update_icon()
- pull_icon.screen_loc = ui_borg_pull
hotkeybuttons += pull_icon
@@ -242,13 +219,13 @@
screenmob.client.screen += module_store_icon //"store" icon
if(!R.module.modules)
- to_chat(usr, "Selected module has no modules to select")
+ to_chat(usr, "Selected module has no modules to select!")
return
if(!R.robot_modules_background)
return
- var/display_rows = CEILING(length(R.module.get_inactive_modules()) / 8, 1)
+ var/display_rows = max(CEILING(length(R.module.get_inactive_modules()) / 8, 1),1)
R.robot_modules_background.screen_loc = "CENTER-4:16,SOUTH+1:7 to CENTER+3:16,SOUTH+[display_rows]:7"
screenmob.client.screen += R.robot_modules_background
@@ -305,3 +282,63 @@
else
for(var/obj/item/I in R.held_items)
screenmob.client.screen -= I
+
+/obj/screen/robot/lamp
+ name = "headlamp"
+ icon_state = "lamp_off"
+ var/mob/living/silicon/robot/robot
+
+/obj/screen/robot/lamp/Click()
+ . = ..()
+ if(.)
+ return
+ robot?.toggle_headlamp()
+ update_icon()
+
+/obj/screen/robot/lamp/update_icon()
+ if(robot?.lamp_enabled)
+ icon_state = "lamp_on"
+ else
+ icon_state = "lamp_off"
+
+/obj/screen/robot/alerts
+ name = "Alert Panel"
+ icon = 'icons/mob/screen_ai.dmi'
+ icon_state = "alerts"
+
+/obj/screen/robot/alerts/Click()
+ . = ..()
+ if(.)
+ return
+ var/mob/living/silicon/robot/borgo = usr
+ borgo.robot_alerts()
+
+/obj/screen/robot/thrusters
+ name = "ion thrusters"
+ icon_state = "ionpulse0"
+
+/obj/screen/robot/thrusters/Click()
+ if(..())
+ return
+ var/mob/living/silicon/robot/R = usr
+ R.toggle_ionpulse()
+
+/obj/screen/robot/sensors
+ name = "Sensor Augmentation"
+ icon_state = "cyborg_sensor"
+
+/obj/screen/robot/sensors/Click()
+ if(..())
+ return
+ var/mob/living/silicon/S = usr
+ S.toggle_sensors()
+/obj/screen/robot/modPC
+ name = "Modular Interface"
+ icon_state = "template"
+ var/mob/living/silicon/robot/robot
+
+/obj/screen/robot/modPC/Click()
+ . = ..()
+ if(.)
+ return
+ robot.modularInterface?.interact(robot)
diff --git a/code/_onclick/hud/screen_objects.dm b/code/_onclick/hud/screen_objects.dm
index 2ed8c81ba2..64515260ec 100644
--- a/code/_onclick/hud/screen_objects.dm
+++ b/code/_onclick/hud/screen_objects.dm
@@ -12,9 +12,14 @@
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
/**
* Map name assigned to this object.
* Automatically set by /client/proc/add_obj_to_map.
@@ -60,7 +65,17 @@
name = "swap hand"
/obj/screen/swap_hand/Click()
- usr.swap_hand()
+ // 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)
+ // return 1
+
+ if(usr.incapacitated())
+ return 1
+
+ if(ismob(usr))
+ var/mob/M = usr
+ M.swap_hand()
return 1
/obj/screen/craft
@@ -96,17 +111,27 @@
H.open_language_menu(usr)
/obj/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.
+ /// The identifier for the slot. It has nothing to do with ID cards.
+ var/slot_id
+ /// Icon when empty. For now used only by humans.
+ var/icon_empty
+ /// Icon when contains an item. For now used only by humans.
+ var/icon_full
+ /// The overlay when hovering over with an item in your hand
var/list/object_overlays = list()
layer = HUD_LAYER
plane = HUD_PLANE
/obj/screen/inventory/Click(location, control, params)
- if(hud?.mymob && (hud.mymob != usr))
- return
- // just redirect clicks
+ // 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)
+ // return TRUE
+
+ if(usr.incapacitated()) // ignore_stasis = TRUE
+ return TRUE
+ if(ismecha(usr.loc)) // stops inventory actions in a mech
+ return TRUE
if(hud?.mymob && slot_id)
var/obj/item/inv_item = hud.mymob.get_item_by_slot(slot_id)
@@ -150,12 +175,13 @@
var/image/item_overlay = image(holding)
item_overlay.alpha = 92
- if(!user.can_equip(holding, slot_id, TRUE, TRUE, TRUE))
+ if(!user.can_equip(holding, slot_id, TRUE))
item_overlay.color = "#FF0000"
else
item_overlay.color = "#00ff00"
- object_overlays += item_overlay
+ cut_overlay(object_overlays)
+ // object_overlay = item_overlay
add_overlay(object_overlays)
/obj/screen/inventory/hand
@@ -187,10 +213,17 @@
/obj/screen/inventory/hand/Click(location, control, params)
- if(hud?.mymob && (hud.mymob != usr))
- return
- var/mob/user = hud.mymob
- // just redirect clicks
+ // 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
+ if(usr != user)
+ return TRUE
+ // if(world.time <= user.next_move)
+ // return TRUE
+ if(user.incapacitated())
+ return TRUE
+ if (ismecha(user.loc)) // stops inventory actions in a mech
+ return TRUE
if(user.active_hand_index == held_index)
var/obj/item/I = user.get_active_held_item()
@@ -318,6 +351,10 @@
icon = 'icons/mob/screen_midnight.dmi'
icon_state = "running"
+/obj/screen/mov_intent/Initialize(mapload)
+ . = ..()
+ update_icon()
+
/obj/screen/mov_intent/Click()
toggle(usr)
@@ -326,7 +363,7 @@
if(MOVE_INTENT_WALK)
icon_state = "walking"
if(MOVE_INTENT_RUN)
- icon_state = "running"
+ icon_state = CONFIG_GET(flag/sprint_enabled)? "running" : "running_nosprint"
/obj/screen/mov_intent/proc/toggle(mob/user)
if(isobserver(user))
diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm
index cd1ee56848..f63e594675 100644
--- a/code/_onclick/item_attack.dm
+++ b/code/_onclick/item_attack.dm
@@ -85,7 +85,7 @@
if(force && damtype != STAMINA && HAS_TRAIT(user, TRAIT_PACIFISM))
to_chat(user, "You don't want to harm other living beings!")
return
-
+
if(!UseStaminaBufferStandard(user, STAM_COST_ATTACK_MOB_MULT, null, TRUE))
return DISCARD_LAST_ACTION
@@ -97,6 +97,9 @@
M.lastattacker = user.real_name
M.lastattackerckey = user.ckey
+ if(force && M == user && user.client)
+ user.client.give_award(/datum/award/achievement/misc/selfouch, user)
+
user.do_attack_animation(M)
M.attacked_by(src, user, attackchain_flags, damage_multiplier)
diff --git a/code/controllers/admin.dm b/code/controllers/admin.dm
index 3782d8be94..19fef28597 100644
--- a/code/controllers/admin.dm
+++ b/code/controllers/admin.dm
@@ -3,7 +3,7 @@
name = "Initializing..."
var/target
-INITIALIZE_IMMEDIATE(/obj/effect/statclick) //it's new, but rebranded.
+INITIALIZE_IMMEDIATE(/obj/effect/statclick)
/obj/effect/statclick/Initialize(mapload, text, target) //Don't port this to Initialize it's too critical
. = ..()
@@ -33,14 +33,6 @@ INITIALIZE_IMMEDIATE(/obj/effect/statclick) //it's new, but rebranded.
usr.client.debug_variables(target)
message_admins("Admin [key_name_admin(usr)] is debugging the [target] [class].")
-/obj/effect/statclick/misc_subsystems/Click()
- if(!usr.client.holder)
- return
- var/subsystem = input(usr, "Debug which subsystem?", "Debug nonprocessing subsystem") as null|anything in (Master.subsystems - Master.statworthy_subsystems)
- if(!subsystem)
- return
- usr.client.debug_variables(subsystem)
- message_admins("Admin [key_name_admin(usr)] is debugging the [subsystem] subsystem.")
// Debug verbs.
/client/proc/restart_controller(controller in list("Master", "Failsafe"))
diff --git a/code/controllers/configuration/configuration.dm b/code/controllers/configuration/configuration.dm
index 9d5110daf5..32da3b5938 100644
--- a/code/controllers/configuration/configuration.dm
+++ b/code/controllers/configuration/configuration.dm
@@ -415,7 +415,7 @@ Example config:
while(recent_round)
adjustment += repeated_mode_adjust[recent_round]
recent_round = SSpersistence.saved_modes.Find(name,recent_round+1,0)
- probability *= ((100-adjustment)/100)
+ probability *= max(0,((100-adjustment)/100))
runnable_storytellers[S] = probability
return runnable_storytellers
@@ -425,6 +425,7 @@ Example config:
var/list/min_pop = Get(/datum/config_entry/keyed_list/min_pop)
var/list/max_pop = Get(/datum/config_entry/keyed_list/max_pop)
var/list/repeated_mode_adjust = Get(/datum/config_entry/number_list/repeated_mode_adjust)
+ var/desired_chaos_level = 9 - SSpersistence.get_recent_chaos()
for(var/T in gamemode_cache)
var/datum/game_mode/M = new T()
if(!(M.config_tag in modes))
@@ -448,7 +449,18 @@ Example config:
while(recent_round)
adjustment += repeated_mode_adjust[recent_round]
recent_round = SSpersistence.saved_modes.Find(M.config_tag,recent_round+1,0)
- final_weight *= ((100-adjustment)/100)
+ final_weight *= max(0,((100-adjustment)/100))
+ if(Get(/datum/config_entry/flag/weigh_by_recent_chaos))
+ var/chaos_level = M.get_chaos()
+ var/exponent = Get(/datum/config_entry/number/chaos_exponent)
+ var/delta = chaos_level - desired_chaos_level
+ if(desired_chaos_level > 5)
+ delta = abs(min(delta, 0))
+ else if(desired_chaos_level < 5)
+ delta = max(delta, 0)
+ else
+ delta = abs(delta)
+ final_weight /= (delta + 1) ** exponent
runnable_modes[M] = final_weight
return runnable_modes
diff --git a/code/controllers/configuration/entries/comms.dm b/code/controllers/configuration/entries/comms.dm
index e56ff3f0d1..00f1cedf4f 100644
--- a/code/controllers/configuration/entries/comms.dm
+++ b/code/controllers/configuration/entries/comms.dm
@@ -22,11 +22,10 @@
/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
+/datum/config_entry/string/cross_comms_network
+ protection = CONFIG_ENTRY_LOCKED
+/// cit config
/datum/config_entry/keyed_list/cross_server_bunker_override
key_mode = KEY_MODE_TEXT
value_mode = VALUE_MODE_TEXT
diff --git a/code/controllers/configuration/entries/game_options.dm b/code/controllers/configuration/entries/game_options.dm
index dc8e9e1859..76a5f5060c 100644
--- a/code/controllers/configuration/entries/game_options.dm
+++ b/code/controllers/configuration/entries/game_options.dm
@@ -7,6 +7,13 @@
/datum/config_entry/keyed_list/probability/ValidateListEntry(key_name)
return key_name in config.modes
+/datum/config_entry/keyed_list/chaos_level
+ key_mode = KEY_MODE_TEXT
+ value_mode = VALUE_MODE_NUM
+
+/datum/config_entry/keyed_list/chaos_level/ValidateListEntry(key_name)
+ return key_name in config.modes
+
/datum/config_entry/keyed_list/max_pop
key_mode = KEY_MODE_TEXT
value_mode = VALUE_MODE_NUM
@@ -290,6 +297,17 @@
var/datum/movespeed_modifier/config_walk_run/M = get_cached_movespeed_modifier(/datum/movespeed_modifier/config_walk_run/walk)
M.sync()
+/datum/config_entry/flag/sprint_enabled
+ config_entry_value = TRUE
+
+/datum/config_entry/flag/sprint_enabled/ValidateAndSet(str_val)
+ . = ..()
+ for(var/datum/hud/human/H)
+ H.assert_move_intent_ui()
+ if(!config_entry_value) // disabled
+ for(var/mob/living/L in world)
+ L.disable_intentional_sprint_mode()
+
/datum/config_entry/number/movedelay/sprint_speed_increase
config_entry_value = 1
@@ -484,6 +502,8 @@
/datum/config_entry/flag/modetier_voting
+/datum/config_entry/flag/must_be_readied_to_vote_gamemode
+
/datum/config_entry/number/dropped_modes
config_entry_value = 3
@@ -583,3 +603,8 @@
/// Dirtyness multiplier for making turfs dirty
/datum/config_entry/number/turf_dirty_multiplier
config_entry_value = 1
+
+/datum/config_entry/flag/weigh_by_recent_chaos
+
+/datum/config_entry/number/chaos_exponent
+ config_entry_value = 1
diff --git a/code/controllers/configuration/entries/general.dm b/code/controllers/configuration/entries/general.dm
index 3c93952b65..63da60d7b5 100644
--- a/code/controllers/configuration/entries/general.dm
+++ b/code/controllers/configuration/entries/general.dm
@@ -26,50 +26,6 @@
/datum/config_entry/flag/hub // if the game appears on the hub or not
-/datum/config_entry/flag/log_ooc // log OOC channel
-
-/datum/config_entry/flag/log_access // log login/logout
-
-/datum/config_entry/flag/log_say // log client say
-
-/datum/config_entry/flag/log_admin // log admin actions
- protection = CONFIG_ENTRY_LOCKED
-
-/datum/config_entry/flag/log_prayer // log prayers
-
-/datum/config_entry/flag/log_law // log lawchanges
-
-/datum/config_entry/flag/log_game // log game events
-
-/datum/config_entry/flag/log_virus // log virology data
-
-/datum/config_entry/flag/log_vote // log voting
-
-/datum/config_entry/flag/log_craft // log crafting
-
-/datum/config_entry/flag/log_whisper // log client whisper
-
-/datum/config_entry/flag/log_attack // log attack messages
-
-/datum/config_entry/flag/log_emote // log emotes
-
-/datum/config_entry/flag/log_adminchat // log admin chat messages
- protection = CONFIG_ENTRY_LOCKED
-
-/datum/config_entry/flag/log_shuttle // log shuttle related actions, ie shuttle computers, shuttle manipulator, emergency console
-
-/datum/config_entry/flag/log_pda // log pda messages
-
-/datum/config_entry/flag/log_telecomms // log telecomms messages
-
-/datum/config_entry/flag/log_twitter // log certain expliotable parrots and other such fun things in a JSON file of twitter valid phrases.
-
-/datum/config_entry/flag/log_world_topic // log all world.Topic() calls
-
-/datum/config_entry/flag/log_manifest // log crew manifest to seperate file
-
-/datum/config_entry/flag/log_job_debug // log roundstart divide occupations debug information to a file
-
/datum/config_entry/flag/allow_admin_ooccolor // Allows admins with relevant permissions to have their own ooc colour
/datum/config_entry/flag/allow_vote_restart // allow votes to restart
@@ -319,6 +275,11 @@
/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."
+
/datum/config_entry/number/notify_new_player_age // how long do we notify admins of a new player
min_val = -1
@@ -472,10 +433,6 @@
/datum/config_entry/string/default_view_square
config_entry_value = "15x15"
-/datum/config_entry/flag/log_pictures
-
-/datum/config_entry/flag/picture_logging_camera
-
/datum/config_entry/number/max_bunker_days
config_entry_value = 7
min_val = 1
diff --git a/code/controllers/configuration/entries/logging.dm b/code/controllers/configuration/entries/logging.dm
new file mode 100644
index 0000000000..1cb47d6ab7
--- /dev/null
+++ b/code/controllers/configuration/entries/logging.dm
@@ -0,0 +1,70 @@
+/datum/config_entry/flag/log_ooc // log OOC channel
+ config_entry_value = TRUE
+
+/datum/config_entry/flag/log_access // log login/logout
+ config_entry_value = TRUE
+
+/datum/config_entry/flag/log_say // log client say
+ config_entry_value = TRUE
+
+/datum/config_entry/flag/log_admin // log admin actions
+ protection = CONFIG_ENTRY_LOCKED
+
+/datum/config_entry/flag/log_prayer // log prayers
+ config_entry_value = TRUE
+
+/datum/config_entry/flag/log_law // log lawchanges
+ config_entry_value = TRUE
+
+/datum/config_entry/flag/log_game // log game events
+ config_entry_value = TRUE
+
+/datum/config_entry/flag/log_virus // log virology data
+ config_entry_value = TRUE
+
+/datum/config_entry/flag/log_vote // log voting
+ config_entry_value = TRUE
+
+/datum/config_entry/flag/log_craft // log crafting
+ config_entry_value = TRUE
+
+/datum/config_entry/flag/log_whisper // log client whisper
+ config_entry_value = TRUE
+
+/datum/config_entry/flag/log_attack // log attack messages
+ config_entry_value = TRUE
+
+/datum/config_entry/flag/log_emote // log emotes
+ config_entry_value = TRUE
+
+/datum/config_entry/flag/log_adminchat // log admin chat messages
+ protection = CONFIG_ENTRY_LOCKED
+
+/datum/config_entry/flag/log_shuttle // log shuttle related actions, ie shuttle computers, shuttle manipulator, emergency console
+ config_entry_value = TRUE
+
+/datum/config_entry/flag/log_pda // log pda messages
+ config_entry_value = TRUE
+
+/datum/config_entry/flag/log_telecomms // log telecomms messages
+ config_entry_value = TRUE
+
+/datum/config_entry/flag/log_twitter // log certain expliotable parrots and other such fun things in a JSON file of twitter valid phrases.
+ config_entry_value = TRUE
+
+/datum/config_entry/flag/log_world_topic // log all world.Topic() calls
+ config_entry_value = TRUE
+
+/datum/config_entry/flag/log_manifest // log crew manifest to seperate file
+ config_entry_value = TRUE
+
+/datum/config_entry/flag/log_job_debug // log roundstart divide occupations debug information to a file
+ config_entry_value = TRUE
+
+/datum/config_entry/flag/log_pictures
+
+/datum/config_entry/flag/picture_logging_camera
+
+/// forces log_href for tgui
+/datum/config_entry/flag/emergency_tgui_logging
+ config_entry_value = FALSE
diff --git a/code/controllers/failsafe.dm b/code/controllers/failsafe.dm
index fdce9a1287..a15056e442 100644
--- a/code/controllers/failsafe.dm
+++ b/code/controllers/failsafe.dm
@@ -1,7 +1,7 @@
/**
- * Failsafe
- *
- * Pretty much pokes the MC to make sure it's still alive.
+ * Failsafe
+ *
+ * Pretty much pokes the MC to make sure it's still alive.
**/
GLOBAL_REAL(Failsafe, /datum/controller/failsafe)
diff --git a/code/controllers/master.dm b/code/controllers/master.dm
index 1b5c7e5e83..59ac68960c 100644
--- a/code/controllers/master.dm
+++ b/code/controllers/master.dm
@@ -1,10 +1,10 @@
- /**
- * StonedMC
- *
- * Designed to properly split up a given tick among subsystems
- * Note: if you read parts of this code and think "why is it doing it that way"
- * Odds are, there is a reason
- *
+/**
+ * StonedMC
+ *
+ * Designed to properly split up a given tick among subsystems
+ * Note: if you read parts of this code and think "why is it doing it that way"
+ * Odds are, there is a reason
+ *
**/
//This is the ABSOLUTE ONLY THING that should init globally like this
@@ -28,8 +28,6 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
// List of subsystems to process().
var/list/subsystems
- /// List of subsystems to include in the MC stat panel.
- var/list/statworthy_subsystems
// Vars for keeping track of tick drift.
var/init_timeofday
@@ -41,7 +39,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
///Only run ticker subsystems for the next n ticks.
var/skip_ticks = 0
- var/make_runtime = 0
+ var/make_runtime = FALSE
var/initializations_finished_with_no_players_logged_in //I wonder what this could be?
@@ -67,9 +65,6 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
//used by CHECK_TICK as well so that the procs subsystems call can obey that SS's tick limits
var/static/current_ticklimit = TICK_LIMIT_RUNNING
- /// Statclick for misc subsystems
- var/obj/effect/statclick/misc_subsystems/misc_statclick
-
/datum/controller/master/New()
if(!config)
config = new
@@ -96,11 +91,6 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
_subsystems += new I
Master = src
- // We want to see all subsystems during init.
- statworthy_subsystems = subsystems.Copy()
-
- misc_statclick = new(null, "Debug")
-
if(!GLOB)
new /datum/controller/global_vars
@@ -217,7 +207,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
// Sort subsystems by display setting for easy access.
sortTim(subsystems, /proc/cmp_subsystem_display)
// Set world options.
- world.fps = CONFIG_GET(number/fps)
+ world.change_fps(CONFIG_GET(number/fps))
var/initialized_tod = REALTIMEOFDAY
if(tgs_prime)
@@ -271,14 +261,10 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
var/list/tickersubsystems = list()
var/list/runlevel_sorted_subsystems = list(list()) //ensure we always have at least one runlevel
var/timer = world.time
- statworthy_subsystems = list()
for (var/thing in subsystems)
var/datum/controller/subsystem/SS = thing
if (SS.flags & SS_NO_FIRE)
- if(SS.flags & SS_ALWAYS_SHOW_STAT)
- statworthy_subsystems += SS
continue
- statworthy_subsystems += SS
SS.queued_time = 0
SS.queue_next = null
SS.queue_prev = null
diff --git a/code/controllers/subsystem.dm b/code/controllers/subsystem.dm
index e49da32557..12798f3863 100644
--- a/code/controllers/subsystem.dm
+++ b/code/controllers/subsystem.dm
@@ -23,7 +23,7 @@
var/priority = FIRE_PRIORITY_DEFAULT
/// [Subsystem Flags][SS_NO_INIT] to control binary behavior. Flags must be set at compile time or before preinit finishes to take full effect. (You can also restart the mc to force them to process again)
- var/flags = 0
+ var/flags = NONE
/// This var is set to TRUE after the subsystem has been initialized.
var/initialized = FALSE
@@ -114,7 +114,7 @@
//previously, this would have been named 'process()' but that name is used everywhere for different things!
//fire() seems more suitable. This is the procedure that gets called every 'wait' deciseconds.
//Sleeping in here prevents future fires until returned.
-/datum/controller/subsystem/proc/fire(resumed = 0)
+/datum/controller/subsystem/proc/fire(resumed = FALSE)
flags |= SS_NO_FIRE
CRASH("Subsystem [src]([type]) does not fire() but did not set the SS_NO_FIRE flag. Please add the SS_NO_FIRE flag to any subsystem that doesn't fire so it doesn't get added to the processing list and waste cpu.")
diff --git a/code/controllers/subsystem/achievements.dm b/code/controllers/subsystem/achievements.dm
new file mode 100644
index 0000000000..f0e10e6f03
--- /dev/null
+++ b/code/controllers/subsystem/achievements.dm
@@ -0,0 +1,74 @@
+SUBSYSTEM_DEF(achievements)
+ name = "Achievements"
+ flags = SS_NO_FIRE
+ init_order = INIT_ORDER_ACHIEVEMENTS
+ var/achievements_enabled = FALSE
+
+ ///List of achievements
+ var/list/datum/award/achievement/achievements = list()
+ ///List of scores
+ var/list/datum/award/score/scores = list()
+ ///List of all awards
+ var/list/datum/award/awards = list()
+
+/datum/controller/subsystem/achievements/Initialize(timeofday)
+ if(!SSdbcore.Connect())
+ return ..()
+ achievements_enabled = TRUE
+
+ for(var/T in subtypesof(/datum/award/achievement))
+ var/instance = new T
+ achievements[T] = instance
+ awards[T] = instance
+
+ for(var/T in subtypesof(/datum/award/score))
+ var/instance = new T
+ scores[T] = instance
+ awards[T] = instance
+
+ update_metadata()
+
+ for(var/i in GLOB.clients)
+ var/client/C = i
+ if(!C.player_details.achievements.initialized)
+ C.player_details.achievements.InitializeData()
+
+ return ..()
+
+/datum/controller/subsystem/achievements/Shutdown()
+ save_achievements_to_db()
+
+/datum/controller/subsystem/achievements/proc/save_achievements_to_db()
+ var/list/cheevos_to_save = list()
+ for(var/ckey in GLOB.player_details)
+ var/datum/player_details/PD = GLOB.player_details[ckey]
+ if(!PD || !PD.achievements)
+ continue
+ cheevos_to_save += PD.achievements.get_changed_data()
+ if(!length(cheevos_to_save))
+ return
+ SSdbcore.MassInsert(format_table_name("achievements"),cheevos_to_save,duplicate_key = TRUE)
+
+//Update the metadata if any are behind
+/datum/controller/subsystem/achievements/proc/update_metadata()
+ var/list/current_metadata = list()
+ //select metadata here
+ var/datum/db_query/Q = SSdbcore.NewQuery("SELECT achievement_key,achievement_version FROM [format_table_name("achievement_metadata")]")
+ if(!Q.Execute(async = TRUE))
+ qdel(Q)
+ return
+ else
+ while(Q.NextRow())
+ current_metadata[Q.item[1]] = text2num(Q.item[2])
+ qdel(Q)
+
+ var/list/to_update = list()
+ for(var/T in awards)
+ var/datum/award/A = awards[T]
+ if(!A.database_id)
+ continue
+ if(!current_metadata[A.database_id] || current_metadata[A.database_id] < A.achievement_version)
+ to_update += list(A.get_metadata_row())
+
+ if(to_update.len)
+ SSdbcore.MassInsert(format_table_name("achievement_metadata"),to_update,duplicate_key = TRUE)
diff --git a/code/controllers/subsystem/air.dm b/code/controllers/subsystem/air.dm
index f777c967ae..3c58c90452 100644
--- a/code/controllers/subsystem/air.dm
+++ b/code/controllers/subsystem/air.dm
@@ -241,7 +241,8 @@ SUBSYSTEM_DEF(air)
return
/datum/controller/subsystem/air/proc/process_turf_equalize(resumed = 0)
- return process_turf_equalize_extools(resumed, (Master.current_ticklimit - TICK_USAGE) * 0.01 * world.tick_lag)
+ if(process_turf_equalize_extools(resumed, (Master.current_ticklimit - TICK_USAGE) * 0.01 * world.tick_lag))
+ pause()
/*
//cache for sanic speed
var/fire_count = times_fired
@@ -260,7 +261,8 @@ SUBSYSTEM_DEF(air)
*/
/datum/controller/subsystem/air/proc/process_active_turfs(resumed = 0)
- return process_active_turfs_extools(resumed, (Master.current_ticklimit - TICK_USAGE) * 0.01 * world.tick_lag)
+ if(process_active_turfs_extools(resumed, (Master.current_ticklimit - TICK_USAGE) * 0.01 * world.tick_lag))
+ pause()
/*
//cache for sanic speed
var/fire_count = times_fired
@@ -278,7 +280,8 @@ SUBSYSTEM_DEF(air)
*/
/datum/controller/subsystem/air/proc/process_excited_groups(resumed = 0)
- return process_excited_groups_extools(resumed, (Master.current_ticklimit - TICK_USAGE) * 0.01 * world.tick_lag)
+ if(process_excited_groups_extools(resumed, (Master.current_ticklimit - TICK_USAGE) * 0.01 * world.tick_lag))
+ pause()
/*
if (!resumed)
src.currentrun = excited_groups.Copy()
diff --git a/code/controllers/subsystem/assets.dm b/code/controllers/subsystem/assets.dm
index 4f02d32ad0..4b43f98290 100644
--- a/code/controllers/subsystem/assets.dm
+++ b/code/controllers/subsystem/assets.dm
@@ -11,7 +11,7 @@ SUBSYSTEM_DEF(assets)
switch (CONFIG_GET(string/asset_transport))
if ("webroot")
newtransporttype = /datum/asset_transport/webroot
-
+
if (newtransporttype == transport.type)
return
diff --git a/code/controllers/subsystem/atoms.dm b/code/controllers/subsystem/atoms.dm
index b9a4785f49..02aad6dec3 100644
--- a/code/controllers/subsystem/atoms.dm
+++ b/code/controllers/subsystem/atoms.dm
@@ -10,33 +10,37 @@ SUBSYSTEM_DEF(atoms)
var/old_initialized
- var/list/late_loaders
+ var/list/late_loaders = list()
var/list/BadInitializeCalls = list()
+ initialized = INITIALIZATION_INSSATOMS
+
/datum/controller/subsystem/atoms/Initialize(timeofday)
GLOB.fire_overlay.appearance_flags = RESET_COLOR
- setupGenetics()
+ setupGenetics() //to set the mutations' sequence
+
initialized = INITIALIZATION_INNEW_MAPLOAD
InitializeAtoms()
+ initialized = INITIALIZATION_INNEW_REGULAR
return ..()
/datum/controller/subsystem/atoms/proc/InitializeAtoms(list/atoms)
if(initialized == INITIALIZATION_INSSATOMS)
return
+ old_initialized = initialized
initialized = INITIALIZATION_INNEW_MAPLOAD
- LAZYINITLIST(late_loaders)
-
var/count
var/list/mapload_arg = list(TRUE)
+
if(atoms)
count = atoms.len
- for(var/I in atoms)
- var/atom/A = I
+ for(var/I in 1 to count)
+ var/atom/A = atoms[I]
if(!(A.flags_1 & INITIALIZED_1))
- InitAtom(I, mapload_arg)
+ InitAtom(A, mapload_arg)
CHECK_TICK
else
count = 0
@@ -49,15 +53,16 @@ SUBSYSTEM_DEF(atoms)
testing("Initialized [count] atoms")
pass(count)
- initialized = INITIALIZATION_INNEW_REGULAR
+ initialized = old_initialized
if(late_loaders.len)
- for(var/I in late_loaders)
- var/atom/A = I
+ for(var/I in 1 to late_loaders.len)
+ var/atom/A = late_loaders[I]
A.LateInitialize()
testing("Late initialized [late_loaders.len] atoms")
late_loaders.Cut()
+/// Init this specific atom
/datum/controller/subsystem/atoms/proc/InitAtom(atom/A, list/arguments)
var/the_type = A.type
if(QDELING(A))
@@ -150,8 +155,3 @@ SUBSYSTEM_DEF(atoms)
var/initlog = InitLog()
if(initlog)
text2file(initlog, "[GLOB.log_directory]/initialize.log")
-
-#undef BAD_INIT_QDEL_BEFORE
-#undef BAD_INIT_DIDNT_INIT
-#undef BAD_INIT_SLEPT
-#undef BAD_INIT_NO_HINT
diff --git a/code/controllers/subsystem/blackbox.dm b/code/controllers/subsystem/blackbox.dm
index dc1f246e3d..5d236045fd 100644
--- a/code/controllers/subsystem/blackbox.dm
+++ b/code/controllers/subsystem/blackbox.dm
@@ -5,33 +5,35 @@ SUBSYSTEM_DEF(blackbox)
runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME
init_order = INIT_ORDER_BLACKBOX
- var/list/feedback = list() //list of datum/feedback_variable
+ var/list/feedback = list() //list of datum/feedback_variable
var/list/first_death = list() //the first death of this round, assoc. vars keep track of different things
var/triggertime = 0
- var/sealed = FALSE //time to stop tracking stats?
+ var/sealed = FALSE //time to stop tracking stats?
var/list/versions = list("antagonists" = 3,
"admin_secrets_fun_used" = 2,
"explosion" = 2,
"time_dilation_current" = 3,
"science_techweb_unlock" = 2,
- "round_end_stats" = 2) //associative list of any feedback variables that have had their format changed since creation and their current version, remember to update this
+ "round_end_stats" = 2,
+ "testmerged_prs" = 2) //associative list of any feedback variables that have had their format changed since creation and their current version, remember to update this
/datum/controller/subsystem/blackbox/Initialize()
triggertime = world.time
record_feedback("amount", "random_seed", Master.random_seed)
record_feedback("amount", "dm_version", DM_VERSION)
+ record_feedback("amount", "dm_build", DM_BUILD)
record_feedback("amount", "byond_version", world.byond_version)
record_feedback("amount", "byond_build", world.byond_build)
. = ..()
//poll population
/datum/controller/subsystem/blackbox/fire()
- set waitfor = FALSE //for population query
+ set waitfor = FALSE //for population query
CheckPlayerCount()
if(CONFIG_GET(flag/use_exp_tracking))
- if((triggertime < 0) || (world.time > (triggertime +3000))) //subsystem fires once at roundstart then once every 10 minutes. a 5 min check skips the first fire. The <0 is midnight rollover check
+ if((triggertime < 0) || (world.time > (triggertime +3000))) //subsystem fires once at roundstart then once every 10 minutes. a 5 min check skips the first fire. The <0 is midnight rollover check
update_exp(10,FALSE)
/datum/controller/subsystem/blackbox/proc/CheckPlayerCount()
@@ -39,12 +41,19 @@ SUBSYSTEM_DEF(blackbox)
if(!SSdbcore.Connect())
return
- var/playercount = 0
- for(var/mob/M in GLOB.player_list)
- if(M.client)
- playercount += 1
+ var/playercount = LAZYLEN(GLOB.player_list)
var/admincount = GLOB.admins.len
- var/datum/DBQuery/query_record_playercount = SSdbcore.NewQuery("INSERT INTO [format_table_name("legacy_population")] (playercount, admincount, time, server_ip, server_port, round_id) VALUES ([playercount], [admincount], '[SQLtime()]', INET_ATON(IF('[world.internet_address]' LIKE '', '0', '[world.internet_address]')), '[world.port]', '[GLOB.round_id]')")
+ var/datum/db_query/query_record_playercount = SSdbcore.NewQuery({"
+ INSERT INTO [format_table_name("legacy_population")] (playercount, admincount, time, server_ip, server_port, round_id)
+ VALUES (:playercount, :admincount, :time, INET_ATON(:server_ip), :server_port, :round_id)
+ "}, list(
+ "playercount" = playercount,
+ "admincount" = admincount,
+ "time" = SQLtime(),
+ "server_ip" = world.internet_address || "0",
+ "server_port" = "[world.port]",
+ "round_id" = GLOB.round_id,
+ ))
query_record_playercount.Execute()
qdel(query_record_playercount)
@@ -88,18 +97,23 @@ SUBSYSTEM_DEF(blackbox)
if (!SSdbcore.Connect())
return
+ var/list/special_columns = list(
+ "datetime" = "NOW()"
+ )
var/list/sqlrowlist = list()
-
for (var/datum/feedback_variable/FV in feedback)
- var/sqlversion = 1
- if(FV.key in versions)
- sqlversion = versions[FV.key]
- sqlrowlist += list(list("datetime" = "Now()", "round_id" = GLOB.round_id, "key_name" = "'[sanitizeSQL(FV.key)]'", "key_type" = "'[FV.key_type]'", "version" = "[sqlversion]", "json" = "'[sanitizeSQL(json_encode(FV.json))]'"))
+ sqlrowlist += list(list(
+ "round_id" = GLOB.round_id,
+ "key_name" = FV.key,
+ "key_type" = FV.key_type,
+ "version" = versions[FV.key] || 1,
+ "json" = json_encode(FV.json)
+ ))
if (!length(sqlrowlist))
return
- SSdbcore.MassInsert(format_table_name("feedback"), sqlrowlist, ignore_errors = TRUE, delayed = TRUE)
+ SSdbcore.MassInsert(format_table_name("feedback"), sqlrowlist, ignore_errors = TRUE, delayed = TRUE, special_columns = special_columns)
/datum/controller/subsystem/blackbox/proc/Seal()
if(sealed)
@@ -157,21 +171,21 @@ feedback data can be recorded in 5 formats:
used for simple single-string records i.e. the current map
further calls to the same key will append saved data unless the overwrite argument is true or it already exists
when encoded calls made with overwrite will lack square brackets
- calls: SSblackbox.record_feedback("text", "example", 1, "sample text")
+ calls: SSblackbox.record_feedback("text", "example", 1, "sample text")
SSblackbox.record_feedback("text", "example", 1, "other text")
json: {"data":["sample text","other text"]}
"amount"
used to record simple counts of data i.e. the number of ahelps received
further calls to the same key will add or subtract (if increment argument is a negative) from the saved amount
- calls: SSblackbox.record_feedback("amount", "example", 8)
+ calls: SSblackbox.record_feedback("amount", "example", 8)
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
further calls to the same key will:
- add or subtract from the saved value of the data key if it already exists
+ 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
- calls: SSblackbox.record_feedback("tally", "example", 1, "sample data")
+ calls: SSblackbox.record_feedback("tally", "example", 1, "sample data")
SSblackbox.record_feedback("tally", "example", 4, "sample data")
SSblackbox.record_feedback("tally", "example", 2, "other data")
json: {"data":{"sample data":5,"other data":2}}
@@ -181,21 +195,21 @@ feedback data can be recorded in 5 formats:
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
further calls to the same key will:
- add or subtract from the saved value of the data key if it already exists in the same multi-dimensional position
+ add or subtract from the saved value of the data key if it already exists in the same multi-dimensional position
append the key and it's value if it doesn't exist
- calls: SSblackbox.record_feedback("nested tally", "example", 1, list("fruit", "orange", "apricot"))
+ calls: SSblackbox.record_feedback("nested tally", "example", 1, list("fruit", "orange", "apricot"))
SSblackbox.record_feedback("nested tally", "example", 2, list("fruit", "orange", "orange"))
SSblackbox.record_feedback("nested tally", "example", 3, list("fruit", "orange", "apricot"))
SSblackbox.record_feedback("nested tally", "example", 10, list("fruit", "red", "apple"))
SSblackbox.record_feedback("nested tally", "example", 1, list("vegetable", "orange", "carrot"))
json: {"data":{"fruit":{"orange":{"apricot":4,"orange":2},"red":{"apple":10}},"vegetable":{"orange":{"carrot":1}}}}
tracking values associated with a number can't merge with a nesting value, trying to do so will append the list
- call: SSblackbox.record_feedback("nested tally", "example", 3, list("fruit", "orange"))
+ call: SSblackbox.record_feedback("nested tally", "example", 3, list("fruit", "orange"))
json: {"data":{"fruit":{"orange":{"apricot":4,"orange":2},"red":{"apple":10},"orange":3},"vegetable":{"orange":{"carrot":1}}}}
"associative"
used to record text that's associated with a value i.e. coordinates
further calls to the same key will append a new list to existing data
- calls: SSblackbox.record_feedback("associative", "example", 1, list("text" = "example", "path" = /obj/item, "number" = 4))
+ calls: SSblackbox.record_feedback("associative", "example", 1, list("text" = "example", "path" = /obj/item, "number" = 4))
SSblackbox.record_feedback("associative", "example", 1, list("number" = 7, "text" = "example", "other text" = "sample"))
json: {"data":{"1":{"text":"example","path":"/obj/item","number":"4"},"2":{"number":"7","text":"example","other text":"sample"}}}
@@ -271,6 +285,18 @@ Versioning
key = new_key
key_type = new_key_type
+/datum/controller/subsystem/blackbox/proc/LogAhelp(ticket, action, message, recipient, sender)
+ if(!SSdbcore.Connect())
+ return
+
+ var/datum/db_query/query_log_ahelp = SSdbcore.NewQuery({"
+ INSERT INTO [format_table_name("ticket")] (ticket, action, message, recipient, sender, server_ip, server_port, round_id, timestamp)
+ VALUES (:ticket, :action, :message, :recipient, :sender, INET_ATON(:server_ip), :server_port, :round_id, :time)
+ "}, list("ticket" = ticket, "action" = action, "message" = message, "recipient" = recipient, "sender" = sender, "server_ip" = world.internet_address || "0", "server_port" = world.port, "round_id" = GLOB.round_id, "time" = SQLtime()))
+ query_log_ahelp.Execute()
+ qdel(query_log_ahelp)
+
+
/datum/controller/subsystem/blackbox/proc/ReportDeath(mob/living/L)
set waitfor = FALSE
if(sealed)
@@ -285,51 +311,39 @@ Versioning
first_death["area"] = "[AREACOORD(L)]"
first_death["damage"] = "[L.getBruteLoss()]/[L.getFireLoss()]/[L.getToxLoss()]/[L.getOxyLoss()]/[L.getCloneLoss()]"
first_death["last_words"] = L.last_words
- var/sqlname = L.real_name
- var/sqlkey = L.ckey
- var/sqljob = L.mind.assigned_role
- var/sqlspecial = L.mind.special_role
- var/sqlpod = get_area_name(L, TRUE)
- var/laname = L.lastattacker
- var/lakey = L.lastattackerckey
- var/sqlbrute = L.getBruteLoss()
- var/sqlfire = L.getFireLoss()
- var/sqlbrain = L.getOrganLoss(ORGAN_SLOT_BRAIN)
- var/sqloxy = L.getOxyLoss()
- var/sqltox = L.getToxLoss()
- var/sqlclone = L.getCloneLoss()
- var/sqlstamina = L.getStaminaLoss()
- var/x_coord = L.x
- var/y_coord = L.y
- var/z_coord = L.z
- var/last_words = L.last_words
- var/suicide = L.suiciding
- var/map = SSmapping.config.map_name
if(!SSdbcore.Connect())
return
- sqlname = sanitizeSQL(sqlname)
- sqlkey = sanitizeSQL(sqlkey)
- sqljob = sanitizeSQL(sqljob)
- sqlspecial = sanitizeSQL(sqlspecial)
- sqlpod = sanitizeSQL(sqlpod)
- laname = sanitizeSQL(laname)
- lakey = sanitizeSQL(lakey)
- sqlbrute = sanitizeSQL(sqlbrute)
- sqlfire = sanitizeSQL(sqlfire)
- sqlbrain = sanitizeSQL(sqlbrain)
- sqloxy = sanitizeSQL(sqloxy)
- sqltox = sanitizeSQL(sqltox)
- sqlclone = sanitizeSQL(sqlclone)
- sqlstamina = sanitizeSQL(sqlstamina)
- x_coord = sanitizeSQL(x_coord)
- y_coord = sanitizeSQL(y_coord)
- z_coord = sanitizeSQL(z_coord)
- last_words = sanitizeSQL(last_words)
- suicide = sanitizeSQL(suicide)
- map = sanitizeSQL(map)
- var/datum/DBQuery/query_report_death = SSdbcore.NewQuery("INSERT INTO [format_table_name("death")] (pod, x_coord, y_coord, z_coord, mapname, server_ip, server_port, round_id, tod, job, special, name, byondkey, laname, lakey, bruteloss, fireloss, brainloss, oxyloss, toxloss, cloneloss, staminaloss, last_words, suicide) VALUES ('[sqlpod]', '[x_coord]', '[y_coord]', '[z_coord]', '[map]', INET_ATON(IF('[world.internet_address]' LIKE '', '0', '[world.internet_address]')), '[world.port]', [GLOB.round_id], '[SQLtime()]', '[sqljob]', '[sqlspecial]', '[sqlname]', '[sqlkey]', '[laname]', '[lakey]', [sqlbrute], [sqlfire], [sqlbrain], [sqloxy], [sqltox], [sqlclone], [sqlstamina], '[last_words]', [suicide])")
+ var/datum/db_query/query_report_death = SSdbcore.NewQuery({"
+ INSERT INTO [format_table_name("death")] (pod, x_coord, y_coord, z_coord, mapname, server_ip, server_port, round_id, tod, job, special, name, byondkey, laname, lakey, bruteloss, fireloss, brainloss, oxyloss, toxloss, cloneloss, staminaloss, last_words, suicide)
+ VALUES (:pod, :x_coord, :y_coord, :z_coord, :map, INET_ATON(:internet_address), :port, :round_id, :time, :job, :special, :name, :key, :laname, :lakey, :brute, :fire, :brain, :oxy, :tox, :clone, :stamina, :last_words, :suicide)
+ "}, list(
+ "name" = L.real_name,
+ "key" = L.ckey,
+ "job" = L.mind.assigned_role,
+ "special" = L.mind.special_role,
+ "pod" = get_area_name(L, TRUE),
+ "laname" = L.lastattacker,
+ "lakey" = L.lastattackerckey,
+ "brute" = L.getBruteLoss(),
+ "fire" = L.getFireLoss(),
+ "brain" = L.getOrganLoss(ORGAN_SLOT_BRAIN) || BRAIN_DAMAGE_DEATH, //getOrganLoss returns null without a brain but a value is required for this column
+ "oxy" = L.getOxyLoss(),
+ "tox" = L.getToxLoss(),
+ "clone" = L.getCloneLoss(),
+ "stamina" = L.getStaminaLoss(),
+ "x_coord" = L.x,
+ "y_coord" = L.y,
+ "z_coord" = L.z,
+ "last_words" = L.last_words,
+ "suicide" = L.suiciding,
+ "map" = SSmapping.config.map_name,
+ "internet_address" = world.internet_address || "0",
+ "port" = "[world.port]",
+ "round_id" = GLOB.round_id,
+ "time" = SQLtime(),
+ ))
if(query_report_death)
query_report_death.Execute(async = TRUE)
qdel(query_report_death)
diff --git a/code/controllers/subsystem/chat.dm b/code/controllers/subsystem/chat.dm
index f2e9da704f..0e4f8ecad2 100644
--- a/code/controllers/subsystem/chat.dm
+++ b/code/controllers/subsystem/chat.dm
@@ -1,4 +1,4 @@
-/**
+/*!
* Copyright (c) 2020 Aleksej Komarov
* SPDX-License-Identifier: MIT
*/
diff --git a/code/controllers/subsystem/dbcore.dm b/code/controllers/subsystem/dbcore.dm
index 4eff4fbf1d..b6b750fbf4 100644
--- a/code/controllers/subsystem/dbcore.dm
+++ b/code/controllers/subsystem/dbcore.dm
@@ -3,7 +3,7 @@ SUBSYSTEM_DEF(dbcore)
flags = SS_BACKGROUND
wait = 1 MINUTES
init_order = INIT_ORDER_DBCORE
- var/const/FAILED_DB_CONNECTION_CUTOFF = 5
+ var/failed_connection_timeout = 0
var/schema_mismatch = 0
var/db_minor = 0
@@ -13,8 +13,7 @@ SUBSYSTEM_DEF(dbcore)
var/last_error
var/list/active_queries = list()
- var/datum/BSQL_Connection/connection
- var/datum/BSQL_Operation/connectOperation
+ var/connection // Arbitrary handle returned from rust_g.
/datum/controller/subsystem/dbcore/Initialize()
//We send warnings to the admins during subsystem init, as the clients will be New'd and messages
@@ -29,7 +28,7 @@ SUBSYSTEM_DEF(dbcore)
/datum/controller/subsystem/dbcore/fire()
for(var/I in active_queries)
- var/datum/DBQuery/Q = I
+ var/datum/db_query/Q = I
if(world.time - Q.last_activity_time > (5 MINUTES))
message_admins("Found undeleted query, please check the server logs and notify coders.")
log_sql("Undeleted query: \"[Q.sql]\" LA: [Q.last_activity] LAT: [Q.last_activity_time]")
@@ -39,24 +38,25 @@ SUBSYSTEM_DEF(dbcore)
/datum/controller/subsystem/dbcore/Recover()
connection = SSdbcore.connection
- connectOperation = SSdbcore.connectOperation
/datum/controller/subsystem/dbcore/Shutdown()
//This is as close as we can get to the true round end before Disconnect() without changing where it's called, defeating the reason this is a subsystem
if(SSdbcore.Connect())
- var/datum/DBQuery/query_round_shutdown = SSdbcore.NewQuery("UPDATE [format_table_name("round")] SET shutdown_datetime = Now(), end_state = '[sanitizeSQL(SSticker.end_state)]' WHERE id = [GLOB.round_id]")
+ var/datum/db_query/query_round_shutdown = SSdbcore.NewQuery(
+ "UPDATE [format_table_name("round")] SET shutdown_datetime = Now(), end_state = :end_state WHERE id = :round_id",
+ list("end_state" = SSticker.end_state, "round_id" = GLOB.round_id)
+ )
query_round_shutdown.Execute()
qdel(query_round_shutdown)
if(IsConnected())
Disconnect()
- world.BSQL_Shutdown()
//nu
/datum/controller/subsystem/dbcore/can_vv_get(var_name)
- return var_name != NAMEOF(src, connection) && var_name != NAMEOF(src, active_queries) && var_name != NAMEOF(src, connectOperation) && ..()
+ return var_name != NAMEOF(src, connection) && var_name != NAMEOF(src, active_queries) && ..()
/datum/controller/subsystem/dbcore/vv_edit_var(var_name, var_value)
- if(var_name == NAMEOF(src, connection) || var_name == NAMEOF(src, connectOperation))
+ if(var_name == NAMEOF(src, connection))
return FALSE
return ..()
@@ -64,7 +64,11 @@ SUBSYSTEM_DEF(dbcore)
if(IsConnected())
return TRUE
- if(failed_connections > FAILED_DB_CONNECTION_CUTOFF) //If it failed to establish a connection more than 5 times in a row, don't bother attempting to connect anymore.
+ if(failed_connection_timeout <= world.time) //it's been more than 5 seconds since we failed to connect, reset the counter
+ failed_connections = 0
+
+ if(failed_connections > 5) //If it failed to establish a connection more than 5 times in a row, don't bother attempting to connect for 5 seconds.
+ failed_connection_timeout = world.time + 50
return FALSE
if(!CONFIG_GET(flag/sql_enabled))
@@ -75,32 +79,33 @@ SUBSYSTEM_DEF(dbcore)
var/db = CONFIG_GET(string/feedback_database)
var/address = CONFIG_GET(string/address)
var/port = CONFIG_GET(number/port)
+ var/timeout = max(CONFIG_GET(number/async_query_timeout), CONFIG_GET(number/blocking_query_timeout))
+ var/thread_limit = CONFIG_GET(number/bsql_thread_limit)
- connection = new /datum/BSQL_Connection(BSQL_CONNECTION_TYPE_MARIADB, CONFIG_GET(number/async_query_timeout), CONFIG_GET(number/blocking_query_timeout), CONFIG_GET(number/bsql_thread_limit))
- var/error
- if(QDELETED(connection))
- connection = null
- error = last_error
+ var/result = json_decode(rustg_sql_connect_pool(json_encode(list(
+ "host" = address,
+ "port" = port,
+ "user" = user,
+ "pass" = pass,
+ "db_name" = db,
+ "read_timeout" = timeout,
+ "write_timeout" = timeout,
+ "max_threads" = thread_limit,
+ ))))
+ . = (result["status"] == "ok")
+ if (.)
+ connection = result["handle"]
else
- SSdbcore.last_error = null
- connectOperation = connection.BeginConnect(address, port, user, pass, db)
- if(SSdbcore.last_error)
- CRASH(SSdbcore.last_error)
- UNTIL(connectOperation.IsComplete())
- error = connectOperation.GetError()
- . = !error
- if (!.)
- last_error = error
- log_sql("Connect() failed | [error]")
+ connection = null
+ last_error = result["data"]
+ log_sql("Connect() failed | [last_error]")
++failed_connections
- QDEL_NULL(connection)
- QDEL_NULL(connectOperation)
/datum/controller/subsystem/dbcore/proc/CheckSchemaVersion()
if(CONFIG_GET(flag/sql_enabled))
if(Connect())
log_world("Database connection established.")
- var/datum/DBQuery/query_db_version = NewQuery("SELECT major, minor FROM [format_table_name("schema_revision")] ORDER BY date DESC LIMIT 1")
+ var/datum/db_query/query_db_version = NewQuery("SELECT major, minor FROM [format_table_name("schema_revision")] ORDER BY date DESC LIMIT 1")
query_db_version.Execute()
if(query_db_version.NextRow())
db_major = text2num(query_db_version.item[1])
@@ -120,47 +125,46 @@ SUBSYSTEM_DEF(dbcore)
/datum/controller/subsystem/dbcore/proc/SetRoundID()
if(!Connect())
return
- var/datum/DBQuery/query_round_initialize = SSdbcore.NewQuery("INSERT INTO [format_table_name("round")] (initialize_datetime, server_ip, server_port) VALUES (Now(), INET_ATON(IF('[world.internet_address]' LIKE '', '0', '[world.internet_address]')), '[world.port]')")
- query_round_initialize.Execute()
+ var/datum/db_query/query_round_initialize = SSdbcore.NewQuery(
+ "INSERT INTO [format_table_name("round")] (initialize_datetime, server_ip, server_port) VALUES (Now(), INET_ATON(:internet_address), :port)",
+ list("internet_address" = world.internet_address || "0", "port" = "[world.port]")
+ )
+ query_round_initialize.Execute(async = FALSE)
+ GLOB.round_id = "[query_round_initialize.last_insert_id]"
qdel(query_round_initialize)
- var/datum/DBQuery/query_round_last_id = SSdbcore.NewQuery("SELECT LAST_INSERT_ID()")
- query_round_last_id.Execute()
- if(query_round_last_id.NextRow())
- GLOB.round_id = query_round_last_id.item[1]
- qdel(query_round_last_id)
/datum/controller/subsystem/dbcore/proc/SetRoundStart()
if(!Connect())
return
- var/datum/DBQuery/query_round_start = SSdbcore.NewQuery("UPDATE [format_table_name("round")] SET start_datetime = Now() WHERE id = [GLOB.round_id]")
+ var/datum/db_query/query_round_start = SSdbcore.NewQuery(
+ "UPDATE [format_table_name("round")] SET start_datetime = Now() WHERE id = :round_id",
+ list("round_id" = GLOB.round_id)
+ )
query_round_start.Execute()
qdel(query_round_start)
/datum/controller/subsystem/dbcore/proc/SetRoundEnd()
if(!Connect())
return
- var/sql_station_name = sanitizeSQL(station_name())
- var/datum/DBQuery/query_round_end = SSdbcore.NewQuery("UPDATE [format_table_name("round")] SET end_datetime = Now(), game_mode_result = '[sanitizeSQL(SSticker.mode_result)]', station_name = '[sql_station_name]' WHERE id = [GLOB.round_id]")
+ var/datum/db_query/query_round_end = SSdbcore.NewQuery(
+ "UPDATE [format_table_name("round")] SET end_datetime = Now(), game_mode_result = :game_mode_result, station_name = :station_name WHERE id = :round_id",
+ list("game_mode_result" = SSticker.mode_result, "station_name" = station_name(), "round_id" = GLOB.round_id)
+ )
query_round_end.Execute()
qdel(query_round_end)
/datum/controller/subsystem/dbcore/proc/Disconnect()
failed_connections = 0
- QDEL_NULL(connectOperation)
- QDEL_NULL(connection)
+ if (connection)
+ rustg_sql_disconnect_pool(connection)
+ connection = null
/datum/controller/subsystem/dbcore/proc/IsConnected()
- if(!CONFIG_GET(flag/sql_enabled))
+ if (!CONFIG_GET(flag/sql_enabled))
return FALSE
- //block until any connect operations finish
- var/datum/BSQL_Connection/_connection = connection
- var/datum/BSQL_Operation/op = connectOperation
- UNTIL(QDELETED(_connection) || op.IsComplete())
- return !QDELETED(connection) && !op.GetError()
-
-/datum/controller/subsystem/dbcore/proc/Quote(str)
- if(connection)
- return connection.Quote(str)
+ if (!connection)
+ return FALSE
+ return json_decode(rustg_sql_connected(connection))["status"] == "online"
/datum/controller/subsystem/dbcore/proc/ErrorMsg()
if(!CONFIG_GET(flag/sql_enabled))
@@ -170,12 +174,33 @@ SUBSYSTEM_DEF(dbcore)
/datum/controller/subsystem/dbcore/proc/ReportError(error)
last_error = error
-/datum/controller/subsystem/dbcore/proc/NewQuery(sql_query)
+/datum/controller/subsystem/dbcore/proc/NewQuery(sql_query, arguments)
if(IsAdminAdvancedProcCall())
log_admin_private("ERROR: Advanced admin proc call led to sql query: [sql_query]. Query has been blocked")
message_admins("ERROR: Advanced admin proc call led to sql query. Query has been blocked")
return FALSE
- return new /datum/DBQuery(sql_query, connection)
+ return new /datum/db_query(connection, sql_query, arguments)
+
+/datum/controller/subsystem/dbcore/proc/QuerySelect(list/querys, warn = FALSE, qdel = FALSE)
+ if (!islist(querys))
+ if (!istype(querys, /datum/db_query))
+ CRASH("Invalid query passed to QuerySelect: [querys]")
+ querys = list(querys)
+
+ for (var/thing in querys)
+ var/datum/db_query/query = thing
+ if (warn)
+ INVOKE_ASYNC(query, /datum/db_query.proc/warn_execute)
+ else
+ INVOKE_ASYNC(query, /datum/db_query.proc/Execute)
+
+ for (var/thing in querys)
+ var/datum/db_query/query = thing
+ UNTIL(!query.in_progress)
+ if (qdel)
+ qdel(query)
+
+
/*
Takes a list of rows (each row being an associated list of column => value) and inserts them via a single mass query.
@@ -184,137 +209,135 @@ You are expected to do your own escaping of the data, and expected to provide yo
The duplicate_key arg can be true to automatically generate this part of the query
or set to a string that is appended to the end of the query
Ignore_errors instructes mysql to continue inserting rows if some of them have errors.
- the erroneous row(s) aren't inserted and there isn't really any way to know why or why errored
+ the erroneous row(s) aren't inserted and there isn't really any way to know why or why errored
Delayed insert mode was removed in mysql 7 and only works with MyISAM type tables,
It was included because it is still supported in mariadb.
It does not work with duplicate_key and the mysql server ignores it in those cases
*/
-/datum/controller/subsystem/dbcore/proc/MassInsert(table, list/rows, duplicate_key = FALSE, ignore_errors = FALSE, delayed = FALSE, warn = FALSE, async = TRUE)
+/datum/controller/subsystem/dbcore/proc/MassInsert(table, list/rows, duplicate_key = FALSE, ignore_errors = FALSE, delayed = FALSE, warn = FALSE, async = TRUE, special_columns = null)
if (!table || !rows || !istype(rows))
return
+
+ // Prepare column list
var/list/columns = list()
- var/list/sorted_rows = list()
-
+ var/list/has_question_mark = list()
for (var/list/row in rows)
- var/list/sorted_row = list()
- sorted_row.len = columns.len
for (var/column in row)
- var/idx = columns[column]
- if (!idx)
- idx = columns.len + 1
- columns[column] = idx
- sorted_row.len = columns.len
+ columns[column] = "?"
+ has_question_mark[column] = TRUE
+ for (var/column in special_columns)
+ columns[column] = special_columns[column]
+ has_question_mark[column] = findtext(special_columns[column], "?")
- sorted_row[idx] = row[column]
- sorted_rows[++sorted_rows.len] = sorted_row
+ // Prepare SQL query full of placeholders
+ var/list/query_parts = list("INSERT")
+ if (delayed)
+ query_parts += " DELAYED"
+ if (ignore_errors)
+ query_parts += " IGNORE"
+ query_parts += " INTO "
+ query_parts += table
+ query_parts += "\n([columns.Join(", ")])\nVALUES"
+
+ var/list/arguments = list()
+ var/has_row = FALSE
+ for (var/list/row in rows)
+ if (has_row)
+ query_parts += ","
+ query_parts += "\n ("
+ var/has_col = FALSE
+ for (var/column in columns)
+ if (has_col)
+ query_parts += ", "
+ if (has_question_mark[column])
+ var/name = "p[arguments.len]"
+ query_parts += replacetext(columns[column], "?", ":[name]")
+ arguments[name] = row[column]
+ else
+ query_parts += columns[column]
+ has_col = TRUE
+ query_parts += ")"
+ has_row = TRUE
if (duplicate_key == TRUE)
var/list/column_list = list()
for (var/column in columns)
column_list += "[column] = VALUES([column])"
- duplicate_key = "ON DUPLICATE KEY UPDATE [column_list.Join(", ")]\n"
- else if (duplicate_key == FALSE)
- duplicate_key = null
+ query_parts += "\nON DUPLICATE KEY UPDATE [column_list.Join(", ")]"
+ else if (duplicate_key != FALSE)
+ query_parts += duplicate_key
- if (ignore_errors)
- ignore_errors = " IGNORE"
- else
- ignore_errors = null
-
- if (delayed)
- delayed = " DELAYED"
- else
- delayed = null
-
- var/list/sqlrowlist = list()
- var/len = columns.len
- for (var/list/row in sorted_rows)
- if (length(row) != len)
- row.len = len
- for (var/value in row)
- if (value == null)
- value = "NULL"
- sqlrowlist += "([row.Join(", ")])"
-
- sqlrowlist = " [sqlrowlist.Join(",\n ")]"
- var/datum/DBQuery/Query = NewQuery("INSERT[delayed][ignore_errors] INTO [table]\n([columns.Join(", ")])\nVALUES\n[sqlrowlist]\n[duplicate_key]")
+ var/datum/db_query/Query = NewQuery(query_parts.Join(), arguments)
if (warn)
. = Query.warn_execute(async)
else
. = Query.Execute(async)
qdel(Query)
-/datum/DBQuery
- var/sql // The sql query being executed.
- var/list/item //list of data values populated by NextRow()
+/datum/db_query
+ // Inputs
+ var/connection
+ var/sql
+ var/arguments
+ // Status information
+ var/in_progress
+ var/last_error
var/last_activity
var/last_activity_time
- var/last_error
- var/skip_next_is_complete
- var/in_progress
- var/datum/BSQL_Connection/connection
- var/datum/BSQL_Operation/Query/query
+ // Output
+ var/list/list/rows
+ var/next_row_to_take = 1
+ var/affected
+ var/last_insert_id
-/datum/DBQuery/New(sql_query, datum/BSQL_Connection/connection)
+ var/list/item //list of data values populated by NextRow()
+
+/datum/db_query/New(connection, sql, arguments)
SSdbcore.active_queries[src] = TRUE
Activity("Created")
item = list()
- src.connection = connection
- sql = sql_query
-/datum/DBQuery/Destroy()
+ src.connection = connection
+ src.sql = sql
+ src.arguments = arguments
+
+/datum/db_query/Destroy()
Close()
SSdbcore.active_queries -= src
return ..()
-/datum/DBQuery/CanProcCall(proc_name)
+/datum/db_query/CanProcCall(proc_name)
//fuck off kevinz
return FALSE
-/datum/DBQuery/proc/SetQuery(new_sql)
- if(in_progress)
- CRASH("Attempted to set new sql while waiting on active query")
- Close()
- sql = new_sql
-
-/datum/DBQuery/proc/Activity(activity)
+/datum/db_query/proc/Activity(activity)
last_activity = activity
last_activity_time = world.time
-/datum/DBQuery/proc/warn_execute(async = FALSE)
+/datum/db_query/proc/warn_execute(async = TRUE)
. = Execute(async)
if(!.)
to_chat(usr, "A SQL error occurred during this operation, check the server logs.")
-/datum/DBQuery/proc/Execute(async = FALSE, log_error = TRUE)
+/datum/db_query/proc/Execute(async = TRUE, log_error = TRUE)
Activity("Execute")
if(in_progress)
CRASH("Attempted to start a new query while waiting on the old one")
- if(QDELETED(connection))
+ if(!SSdbcore.IsConnected())
last_error = "No connection!"
return FALSE
var/start_time
- var/timed_out
if(!async)
start_time = REALTIMEOFDAY
Close()
- query = connection.BeginQuery(sql)
- if(!async)
- timed_out = !query.WaitForCompletion()
- else
- in_progress = TRUE
- UNTIL(query.IsComplete())
- in_progress = FALSE
- skip_next_is_complete = TRUE
- var/error = QDELETED(query) ? "Query object deleted!" : query.GetError()
- last_error = error
- . = !error
+ . = run_query(async)
+ var/timed_out = !. && findtext(last_error, "Operation timed out")
if(!. && log_error)
- log_sql("[error] | Query used: [sql]")
+ log_sql("[last_error] | Query used: [sql] | Arguments: [json_encode(arguments)]")
if(!async && timed_out)
log_query_debug("Query execution started at [start_time]")
log_query_debug("Query execution ended at [REALTIMEOFDAY]")
@@ -322,44 +345,51 @@ Delayed insert mode was removed in mysql 7 and only works with MyISAM type table
log_query_debug("Query used: [sql]")
slow_query_check()
-/datum/DBQuery/proc/slow_query_check()
+/datum/db_query/proc/run_query(async)
+ var/job_result_str
+
+ if (async)
+ var/job_id = rustg_sql_query_async(connection, sql, json_encode(arguments))
+ in_progress = TRUE
+ UNTIL((job_result_str = rustg_sql_check_query(job_id)) != RUSTG_JOB_NO_RESULTS_YET)
+ in_progress = FALSE
+
+ if (job_result_str == RUSTG_JOB_ERROR)
+ last_error = job_result_str
+ return FALSE
+ else
+ job_result_str = rustg_sql_query_blocking(connection, sql, json_encode(arguments))
+
+ var/result = json_decode(job_result_str)
+ switch (result["status"])
+ if ("ok")
+ rows = result["rows"]
+ affected = result["affected"]
+ last_insert_id = result["last_insert_id"]
+ return TRUE
+ if ("err")
+ last_error = result["data"]
+ return FALSE
+ if ("offline")
+ last_error = "offline"
+ return FALSE
+
+/datum/db_query/proc/slow_query_check()
message_admins("HEY! A database query timed out. Did the server just hang? \[YES\]|\[NO\]")
-/datum/DBQuery/proc/NextRow(async)
+/datum/db_query/proc/NextRow(async = TRUE)
Activity("NextRow")
- UNTIL(!in_progress)
- if(!skip_next_is_complete)
- if(!async)
- query.WaitForCompletion()
- else
- in_progress = TRUE
- UNTIL(query.IsComplete())
- in_progress = FALSE
+
+ if (rows && next_row_to_take <= rows.len)
+ item = rows[next_row_to_take]
+ next_row_to_take++
+ return !!item
else
- skip_next_is_complete = FALSE
+ return FALSE
- last_error = query.GetError()
- var/list/results = query.CurrentRow()
- . = results != null
-
- item.Cut()
- //populate item array
- for(var/I in results)
- item += results[I]
-
-/datum/DBQuery/proc/ErrorMsg()
+/datum/db_query/proc/ErrorMsg()
return last_error
-/datum/DBQuery/proc/Close()
- item.Cut()
- QDEL_NULL(query)
-
-/world/BSQL_Debug(message)
- if(!CONFIG_GET(flag/bsql_debug))
- return
-
- //strip sensitive stuff
- if(findtext(message, ": CreateConnection("))
- message = "CreateConnection CENSORED"
-
- log_sql("BSQL_DEBUG: [message]")
+/datum/db_query/proc/Close()
+ rows = null
+ item = null
diff --git a/code/controllers/subsystem/events.dm b/code/controllers/subsystem/events.dm
index 9bc8a631c1..3660770596 100644
--- a/code/controllers/subsystem/events.dm
+++ b/code/controllers/subsystem/events.dm
@@ -25,7 +25,7 @@ SUBSYSTEM_DEF(events)
return ..()
-/datum/controller/subsystem/events/fire(resumed = 0)
+/datum/controller/subsystem/events/fire(resumed = FALSE)
if(!resumed)
checkEvent() //only check these if we aren't resuming a paused fire
src.currentrun = running.Copy()
@@ -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)
@@ -91,13 +91,13 @@ SUBSYSTEM_DEF(events)
if(. == EVENT_CANT_RUN)//we couldn't run this event for some reason, set its max_occurrences to 0
E.max_occurrences = 0
else if(. == EVENT_READY)
- E.random = TRUE
- E.runEvent(TRUE)
+ E.runEvent(random = TRUE)
//allows a client to trigger an event
//aka Badmin Central
// > Not in modules/admin
// REEEEEEEEE
+// Why the heck is this here! Took me so damn long to find!
/client/proc/forceEvent()
set name = "Trigger Event"
set category = "Admin.Events"
diff --git a/code/controllers/subsystem/fire_burning.dm b/code/controllers/subsystem/fire_burning.dm
index 3251285ade..f81c23d186 100644
--- a/code/controllers/subsystem/fire_burning.dm
+++ b/code/controllers/subsystem/fire_burning.dm
@@ -18,6 +18,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]
@@ -28,10 +29,12 @@ SUBSYSTEM_DEF(fire_burning)
return
continue
- if(O.resistance_flags & ON_FIRE)
- O.take_damage(20, BURN, "fire", 0)
- else
- processing -= O
+
+ 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(10 * delta_time, BURN, "fire", 0)
+ else
+ O.extinguish()
if (MC_TICK_CHECK)
return
diff --git a/code/controllers/subsystem/garbage.dm b/code/controllers/subsystem/garbage.dm
index da3bb24a2f..2d2fac1d13 100644
--- a/code/controllers/subsystem/garbage.dm
+++ b/code/controllers/subsystem/garbage.dm
@@ -1,3 +1,26 @@
+/*!
+## Debugging GC issues
+
+In order to debug `qdel()` failures, there are several tools available.
+To enable these tools, define `TESTING` in [_compile_options.dm](https://github.com/tgstation/-tg-station/blob/master/code/_compile_options.dm).
+
+First is a verb called "Find References", which lists **every** refererence to an object in the world. This allows you to track down any indirect or obfuscated references that you might have missed.
+
+Complementing this is another verb, "qdel() then Find References".
+This does exactly what you'd expect; it calls `qdel()` on the object and then it finds all references remaining.
+This is great, because it means that `Destroy()` will have been called before it starts to find references,
+so the only references you'll find will be the ones preventing the object from `qdel()`ing gracefully.
+
+If you have a datum or something you are not destroying directly (say via the singulo),
+the next tool is `QDEL_HINT_FINDREFERENCE`. You can return this in `Destroy()` (where you would normally `return ..()`),
+to print a list of references once it enters the GC queue.
+
+Finally is a verb, "Show qdel() Log", which shows the deletion log that the garbage subsystem keeps. This is helpful if you are having race conditions or need to review the order of deletions.
+
+Note that for any of these tools to work `TESTING` must be defined.
+By using these methods of finding references, you can make your life far, far easier when dealing with `qdel()` failures.
+*/
+
SUBSYSTEM_DEF(garbage)
name = "Garbage"
priority = FIRE_PRIORITY_GARBAGE
@@ -6,7 +29,7 @@ SUBSYSTEM_DEF(garbage)
runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY
init_order = INIT_ORDER_GARBAGE
- var/list/collection_timeout = list(15 SECONDS, 30 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,10 +47,8 @@ SUBSYSTEM_DEF(garbage)
//Queue
var/list/queues
-
#ifdef LEGACY_REFERENCE_TRACKING
var/list/reference_find_on_fail = list()
- var/list/reference_find_on_fail_types = list()
#endif
@@ -99,6 +120,9 @@ SUBSYSTEM_DEF(garbage)
state = SS_RUNNING
break
+
+
+
/datum/controller/subsystem/garbage/proc/HandleQueue(level = GC_QUEUE_CHECK)
if (level == GC_QUEUE_CHECK)
delslasttick = 0
@@ -139,7 +163,7 @@ SUBSYSTEM_DEF(garbage)
++totalgcs
pass_counts[level]++
#ifdef LEGACY_REFERENCE_TRACKING
- reference_find_on_fail -= refID //It's deleted we don't care anymore.
+ reference_find_on_fail -= refID //It's deleted we don't care anymore.
#endif
if (MC_TICK_CHECK)
return
@@ -153,10 +177,10 @@ SUBSYSTEM_DEF(garbage)
D.find_references()
#elif defined(LEGACY_REFERENCE_TRACKING)
if(reference_find_on_fail[refID])
- D.find_references()
+ D.find_references_legacy()
#ifdef GC_FAILURE_HARD_LOOKUP
else
- D.find_references()
+ D.find_references_legacy()
#endif
reference_find_on_fail -= refID
#endif
@@ -190,24 +214,6 @@ SUBSYSTEM_DEF(garbage)
queue.Cut(1,count+1)
count = 0
-/datum/controller/subsystem/garbage/proc/Queue(datum/D, level = GC_QUEUE_CHECK)
- if (isnull(D))
- return
- if (level > GC_QUEUE_COUNT)
- HardDelete(D)
- return
- var/gctime = world.time
- var/refid = "\ref[D]"
-
-#ifdef LEGACY_REFERENCE_TRACKING
- if(reference_find_on_fail_types[D.type])
- reference_find_on_fail["\ref[D]"] = TRUE
-#endif
-
- D.gc_destroyed = gctime
- var/list/queue = queues[level]
- queue[++queue.len] = list(gctime, refid) // not += for byond reasons
-
#ifdef LEGACY_REFERENCE_TRACKING
/datum/controller/subsystem/garbage/proc/add_type_to_findref(type)
if(!ispath(type))
@@ -223,6 +229,24 @@ SUBSYSTEM_DEF(garbage)
reference_find_on_fail_types = list()
#endif
+/datum/controller/subsystem/garbage/proc/Queue(datum/D, level = GC_QUEUE_CHECK)
+ if (isnull(D))
+ return
+ if (level > GC_QUEUE_COUNT)
+ HardDelete(D)
+ return
+ var/gctime = world.time
+ var/refid = "\ref[D]"
+
+#ifdef LEGACY_REFERENCE_TRACKING
+ if(reference_find_on_fail_types[D.type])
+ SSgarbage.reference_find_on_fail[REF(D)] = TRUE
+#endif
+
+ D.gc_destroyed = gctime
+ var/list/queue = queues[level]
+ 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)
var/time = world.timeofday
@@ -275,8 +299,10 @@ SUBSYSTEM_DEF(garbage)
/datum/qdel_item/New(mytype)
name = "[mytype]"
-// 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.
+
+/// 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(!istype(D))
del(D)
@@ -331,9 +357,10 @@ SUBSYSTEM_DEF(garbage)
#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)
+ D.find_references_legacy()
if (QDEL_HINT_IFFAIL_FINDREFERENCE)
SSgarbage.Queue(D)
- SSgarbage.reference_find_on_fail["\ref[D]"] = TRUE
+ SSgarbage.reference_find_on_fail[REF(D)] = TRUE
#endif
else
#ifdef TESTING
diff --git a/code/controllers/subsystem/idlenpcpool.dm b/code/controllers/subsystem/idlenpcpool.dm
index 03b7931d82..ccdaa555a2 100644
--- a/code/controllers/subsystem/idlenpcpool.dm
+++ b/code/controllers/subsystem/idlenpcpool.dm
@@ -33,8 +33,9 @@ SUBSYSTEM_DEF(idlenpcpool)
while(currentrun.len)
var/mob/living/simple_animal/SA = currentrun[currentrun.len]
--currentrun.len
- if (!SA)
+ if (QDELETED(SA))
GLOB.simple_animals[AI_IDLE] -= SA
+ log_world("Found a null in simple_animals list!")
continue
if(!SA.ckey)
diff --git a/code/controllers/subsystem/ipintel.dm b/code/controllers/subsystem/ipintel.dm
index fca394924d..fb0ddead09 100644
--- a/code/controllers/subsystem/ipintel.dm
+++ b/code/controllers/subsystem/ipintel.dm
@@ -2,13 +2,13 @@ SUBSYSTEM_DEF(ipintel)
name = "XKeyScore"
init_order = INIT_ORDER_XKEYSCORE
flags = SS_NO_FIRE
- var/enabled = 0 //disable at round start to avoid checking reconnects
+ var/enabled = FALSE //disable at round start to avoid checking reconnects
var/throttle = 0
var/errors = 0
var/list/cache = list()
/datum/controller/subsystem/ipintel/Initialize(timeofday, zlevel)
- enabled = 1
+ enabled = TRUE
. = ..()
diff --git a/code/controllers/subsystem/job.dm b/code/controllers/subsystem/job.dm
index 779ee2fbac..c090d7367c 100644
--- a/code/controllers/subsystem/job.dm
+++ b/code/controllers/subsystem/job.dm
@@ -490,6 +490,43 @@ SUBSYSTEM_DEF(job)
job.after_spawn(H, M, joined_late) // note: this happens before the mob has a key! M will always have a client, H might not.
equip_loadout(N, H, TRUE)//CIT CHANGE - makes players spawn with in-backpack loadout items properly. A little hacky but it works
+ if(ishuman(H) && H.client && N)
+ if(H.client && H.client.prefs && length(H.client.prefs.tcg_cards))
+ var/obj/item/tcgcard_binder/binder = new(get_turf(H))
+ H.equip_to_slot_if_possible(binder, SLOT_IN_BACKPACK, disable_warning = TRUE, bypass_equip_delay_self = TRUE)
+ for(var/card_type in H.client.prefs.tcg_cards)
+ if(card_type)
+ if(islist(H.client.prefs.tcg_cards[card_type]))
+ for(var/duplicate in H.client.prefs.tcg_cards[card_type])
+ var/obj/item/tcg_card/card = new(get_turf(H), card_type, duplicate)
+ card.forceMove(binder)
+ binder.cards.Add(card)
+ else
+ var/obj/item/tcg_card/card = new(get_turf(H), card_type, H.client.prefs.tcg_cards[card_type])
+ card.forceMove(binder)
+ binder.cards.Add(card)
+ binder.check_for_exodia()
+ if(length(H.client.prefs.tcg_decks))
+ binder.decks = H.client.prefs.tcg_decks
+ else
+ if(H && N.client.prefs && length(N.client.prefs.tcg_cards))
+ var/obj/item/tcgcard_binder/binder = new(get_turf(H))
+ H.equip_to_slot_if_possible(binder, SLOT_IN_BACKPACK, disable_warning = TRUE, bypass_equip_delay_self = TRUE)
+ for(var/card_type in N.client.prefs.tcg_cards)
+ if(card_type)
+ if(islist(H.client.prefs.tcg_cards[card_type]))
+ for(var/duplicate in N.client.prefs.tcg_cards[card_type])
+ var/obj/item/tcg_card/card = new(get_turf(H), card_type, duplicate)
+ card.forceMove(binder)
+ binder.cards.Add(card)
+ else
+ var/obj/item/tcg_card/card = new(get_turf(H), card_type, N.client.prefs.tcg_cards[card_type])
+ card.forceMove(binder)
+ binder.cards.Add(card)
+ binder.check_for_exodia()
+ if(length(N.client.prefs.tcg_decks))
+ binder.decks = N.client.prefs.tcg_decks
+
return H
/*
/datum/controller/subsystem/job/proc/handle_auto_deadmin_roles(client/C, rank)
@@ -691,21 +728,29 @@ SUBSYSTEM_DEF(job)
if(!permitted)
continue
var/obj/item/I = new G.path
- if(I && length(i[LOADOUT_COLOR])) //handle loadout colors
- //handle polychromic items
- if((G.loadout_flags & LOADOUT_CAN_COLOR_POLYCHROMIC) && length(G.loadout_initial_colors))
- var/datum/element/polychromic/polychromic = I.comp_lookup["item_worn_overlays"] //stupid way to do it but GetElement does not work for this
- if(polychromic && istype(polychromic))
- var/list/polychromic_entry = polychromic.colors_by_atom[I]
- if(polychromic_entry)
- if(polychromic.suits_with_helmet_typecache[I.type]) //is this one of those toggleable hood/helmet things?
- polychromic.connect_helmet(I,i[LOADOUT_COLOR])
- polychromic.colors_by_atom[I] = i[LOADOUT_COLOR]
- I.update_icon()
- else
- //handle non-polychromic items (they only have one color)
- I.add_atom_colour(i[LOADOUT_COLOR][1], FIXED_COLOUR_PRIORITY)
- I.update_icon()
+ if(I)
+ if(length(i[LOADOUT_COLOR])) //handle loadout colors
+ //handle polychromic items
+ if((G.loadout_flags & LOADOUT_CAN_COLOR_POLYCHROMIC) && length(G.loadout_initial_colors))
+ var/datum/element/polychromic/polychromic = I.comp_lookup["item_worn_overlays"] //stupid way to do it but GetElement does not work for this
+ if(polychromic && istype(polychromic))
+ var/list/polychromic_entry = polychromic.colors_by_atom[I]
+ if(polychromic_entry)
+ if(polychromic.suits_with_helmet_typecache[I.type]) //is this one of those toggleable hood/helmet things?
+ polychromic.connect_helmet(I,i[LOADOUT_COLOR])
+ polychromic.colors_by_atom[I] = i[LOADOUT_COLOR]
+ I.update_icon()
+ else
+ //handle non-polychromic items (they only have one color)
+ I.add_atom_colour(i[LOADOUT_COLOR][1], FIXED_COLOUR_PRIORITY)
+ I.update_icon()
+ //when inputting the data it's already sanitized
+ if(i[LOADOUT_CUSTOM_NAME])
+ var/custom_name = i[LOADOUT_CUSTOM_NAME]
+ I.name = custom_name
+ if(i[LOADOUT_CUSTOM_DESCRIPTION])
+ var/custom_description = i[LOADOUT_CUSTOM_DESCRIPTION]
+ I.desc = custom_description
if(!M.equip_to_slot_if_possible(I, G.slot, disable_warning = TRUE, bypass_equip_delay_self = TRUE)) // If the job's dresscode compliant, try to put it in its slot, first
if(iscarbon(M))
var/mob/living/carbon/C = M
diff --git a/code/controllers/subsystem/jukeboxes.dm b/code/controllers/subsystem/jukeboxes.dm
index 3ebeaf71ea..7be6d44a7f 100644
--- a/code/controllers/subsystem/jukeboxes.dm
+++ b/code/controllers/subsystem/jukeboxes.dm
@@ -94,8 +94,8 @@ SUBSYSTEM_DEF(jukeboxes)
stack_trace("Nonexistant or invalid object associated with jukebox.")
continue
var/sound/song_played = sound(juketrack.song_path)
- var/area/currentarea = get_area(jukebox)
var/turf/currentturf = get_turf(jukebox)
+ var/area/currentarea = get_area(jukebox)
var/list/hearerscache = hearers(7, jukebox)
song_played.falloff = jukeinfo[4]
@@ -116,7 +116,6 @@ SUBSYSTEM_DEF(jukeboxes)
inrange = TRUE
else
song_played.status = SOUND_MUTE | SOUND_UPDATE //Setting volume = 0 doesn't let the sound properties update at all, which is lame.
-
M.playsound_local(currentturf, null, 100, channel = jukeinfo[2], S = song_played, envwet = (inrange ? -250 : 0), envdry = (inrange ? 0 : -10000))
CHECK_TICK
return
diff --git a/code/controllers/subsystem/lighting.dm b/code/controllers/subsystem/lighting.dm
index e193c68536..19345f3c8d 100644
--- a/code/controllers/subsystem/lighting.dm
+++ b/code/controllers/subsystem/lighting.dm
@@ -6,6 +6,7 @@ SUBSYSTEM_DEF(lighting)
name = "Lighting"
wait = 2
init_order = INIT_ORDER_LIGHTING
+ flags = SS_TICKER
/datum/controller/subsystem/lighting/stat_entry(msg)
msg = "L:[length(GLOB.lighting_update_lights)]|C:[length(GLOB.lighting_update_corners)]|O:[length(GLOB.lighting_update_objects)]"
diff --git a/code/controllers/subsystem/machines.dm b/code/controllers/subsystem/machines.dm
index f356009569..23190574d8 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()
@@ -27,7 +28,7 @@ SUBSYSTEM_DEF(machines)
return ..()
-/datum/controller/subsystem/machines/fire(resumed = 0)
+/datum/controller/subsystem/machines/fire(resumed = FALSE)
if (!resumed)
for(var/datum/powernet/Powernet in powernets)
Powernet.reset() //reset the power state.
@@ -36,11 +37,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 90908bbde0..b5dfec3c44 100644
--- a/code/controllers/subsystem/mapping.dm
+++ b/code/controllers/subsystem/mapping.dm
@@ -286,7 +286,9 @@ SUBSYSTEM_DEF(mapping)
setup_station_z_index()
if(SSdbcore.Connect())
- var/datum/DBQuery/query_round_map_name = SSdbcore.NewQuery("UPDATE [format_table_name("round")] SET map_name = '[config.map_name]' WHERE id = [GLOB.round_id]")
+ var/datum/db_query/query_round_map_name = SSdbcore.NewQuery({"
+ UPDATE [format_table_name("round")] SET map_name = :map_name WHERE id = :round_id
+ "}, list("map_name" = config.map_name, "round_id" = GLOB.round_id))
query_round_map_name.Execute()
qdel(query_round_map_name)
diff --git a/code/controllers/subsystem/materials.dm b/code/controllers/subsystem/materials.dm
index 23d5a7a2b7..2134be0176 100644
--- a/code/controllers/subsystem/materials.dm
+++ b/code/controllers/subsystem/materials.dm
@@ -1,6 +1,8 @@
/*! How material datums work
Materials are now instanced datums, with an associative list of them being kept in SSmaterials. We only instance the materials once and then re-use these instances for everything.
+
These materials call on_applied() on whatever item they are applied to, common effects are adding components, changing color and changing description. This allows us to differentiate items based on the material they are made out of.area
+
*/
SUBSYSTEM_DEF(materials)
@@ -14,12 +16,16 @@ SUBSYSTEM_DEF(materials)
var/list/materialtypes_by_category
///A cache of all material combinations that have been used
var/list/list/material_combos
- ///List of stackcrafting recipes for materials using rigid materials
+ ///List of stackcrafting recipes for materials using base recipes
+ var/list/base_stack_recipes = list(
+ new /datum/stack_recipe("Chair", /obj/structure/chair/greyscale, one_per_turf = TRUE, on_floor = TRUE, applies_mats = TRUE),
+ new /datum/stack_recipe("Toilet", /obj/structure/toilet/greyscale, one_per_turf = TRUE, on_floor = TRUE, applies_mats = TRUE),
+ new /datum/stack_recipe("Sink Frame", /obj/structure/sink/greyscale, one_per_turf = TRUE, on_floor = TRUE, applies_mats = TRUE),
+ new /datum/stack_recipe("Floor tile", /obj/item/stack/tile/material, 1, 4, 20, applies_mats = TRUE),
+ )
+ ///List of stackcrafting recipes for materials using rigid recipes
var/list/rigid_stack_recipes = list(
- new /datum/stack_recipe("chair", /obj/structure/chair/greyscale, one_per_turf = TRUE, on_floor = TRUE, applies_mats = TRUE),
- new /datum/stack_recipe("toilet", /obj/structure/toilet/greyscale, one_per_turf = TRUE, on_floor = TRUE, applies_mats = TRUE),
- new /datum/stack_recipe("sink", /obj/structure/sink/greyscale, one_per_turf = TRUE, on_floor = TRUE, applies_mats = TRUE),
- new /datum/stack_recipe("Floor tile", /obj/item/stack/tile/material, 1, 4, 20, applies_mats = TRUE)
+ // new /datum/stack_recipe("Carving block", /obj/structure/carving_block, 5, one_per_turf = TRUE, on_floor = TRUE, applies_mats = TRUE),
)
///Ran on initialize, populated the materials and materials_by_category dictionaries with their appropiate vars (See these variables for more info)
@@ -29,7 +35,11 @@ SUBSYSTEM_DEF(materials)
materialtypes_by_category = list()
material_combos = list()
for(var/type in subtypesof(/datum/material))
- var/datum/material/ref = new type
+ var/datum/material/ref = type
+ // if(!(initial(ref.init_flags) & MATERIAL_INIT_MAPLOAD))
+ // continue // Do not initialize
+
+ ref = new ref
materials[type] = ref
for(var/c in ref.categories)
materials_by_category[c] += list(ref)
@@ -40,7 +50,6 @@ SUBSYSTEM_DEF(materials)
InitializeMaterials()
return materials[fakemat] || fakemat
-
///Returns a list to be used as an object's custom_materials. Lists will be cached and re-used based on the parameters.
/datum/controller/subsystem/materials/proc/FindOrCreateMaterialCombo(list/materials_declaration, multiplier)
if(!material_combos)
diff --git a/code/controllers/subsystem/medals.dm b/code/controllers/subsystem/medals.dm
deleted file mode 100644
index 36be23973c..0000000000
--- a/code/controllers/subsystem/medals.dm
+++ /dev/null
@@ -1,87 +0,0 @@
-SUBSYSTEM_DEF(medals)
- name = "Medals"
- flags = SS_NO_FIRE
- var/hub_enabled = FALSE
-
-/datum/controller/subsystem/medals/Initialize(timeofday)
- if(CONFIG_GET(string/medal_hub_address) && CONFIG_GET(string/medal_hub_password))
- hub_enabled = TRUE
- return ..()
-
-/datum/controller/subsystem/medals/proc/UnlockMedal(medal, client/player)
- set waitfor = FALSE
- if(!medal || !hub_enabled)
- return
- if(isnull(world.SetMedal(medal, player, CONFIG_GET(string/medal_hub_address), CONFIG_GET(string/medal_hub_password))))
- hub_enabled = FALSE
- log_game("MEDAL ERROR: Could not contact hub to award medal:[medal] player:[player.key]")
- message_admins("Error! Failed to contact hub to award [medal] medal to [player.key]!")
- return
- to_chat(player, "Achievement unlocked: [medal]!")
-
-
-/datum/controller/subsystem/medals/proc/SetScore(score, client/player, increment, force)
- set waitfor = FALSE
- if(!score || !hub_enabled)
- return
-
- var/list/oldscore = GetScore(score, player, TRUE)
- if(increment)
- if(!oldscore[score])
- oldscore[score] = 1
- else
- oldscore[score] = (text2num(oldscore[score]) + 1)
- else
- oldscore[score] = force
-
- var/newscoreparam = list2params(oldscore)
-
- if(isnull(world.SetScores(player.ckey, newscoreparam, CONFIG_GET(string/medal_hub_address), CONFIG_GET(string/medal_hub_password))))
- hub_enabled = FALSE
- log_game("SCORE ERROR: Could not contact hub to set score. Score:[score] player:[player.key]")
- message_admins("Error! Failed to contact hub to set [score] score for [player.key]!")
-
-/datum/controller/subsystem/medals/proc/GetScore(score, client/player, returnlist)
- if(!score || !hub_enabled)
- return
-
- var/scoreget = world.GetScores(player.ckey, score, CONFIG_GET(string/medal_hub_address), CONFIG_GET(string/medal_hub_password))
- if(isnull(scoreget))
- hub_enabled = FALSE
- log_game("SCORE ERROR: Could not contact hub to get score. Score:[score] player:[player.key]")
- message_admins("Error! Failed to contact hub to get score: [score] for [player.key]!")
- return
- . = params2list(scoreget)
- if(!returnlist)
- return .[score]
-
-/datum/controller/subsystem/medals/proc/CheckMedal(medal, client/player)
- if(!medal || !hub_enabled)
- return
-
- if(isnull(world.GetMedal(medal, player, CONFIG_GET(string/medal_hub_address), CONFIG_GET(string/medal_hub_password))))
- hub_enabled = FALSE
- log_game("MEDAL ERROR: Could not contact hub to get medal:[medal] player: [player.key]")
- message_admins("Error! Failed to contact hub to get [medal] medal for [player.key]!")
- return
- to_chat(player, "[medal] is unlocked")
-
-/datum/controller/subsystem/medals/proc/LockMedal(medal, client/player)
- if(!player || !medal || !hub_enabled)
- return
- var/result = world.ClearMedal(medal, player, CONFIG_GET(string/medal_hub_address), CONFIG_GET(string/medal_hub_password))
- switch(result)
- if(null)
- hub_enabled = FALSE
- log_game("MEDAL ERROR: Could not contact hub to clear medal:[medal] player:[player.key]")
- message_admins("Error! Failed to contact hub to clear [medal] medal for [player.key]!")
- if(TRUE)
- message_admins("Medal: [medal] removed for [player.key]")
- if(FALSE)
- message_admins("Medal: [medal] was not found for [player.key]. Unable to clear.")
-
-
-/datum/controller/subsystem/medals/proc/ClearScore(client/player)
- if(isnull(world.SetScores(player.ckey, "", CONFIG_GET(string/medal_hub_address), CONFIG_GET(string/medal_hub_password))))
- log_game("MEDAL ERROR: Could not contact hub to clear scores for [player.key]!")
- message_admins("Error! Failed to contact hub to clear scores for [player.key]!")
diff --git a/code/controllers/subsystem/minimum_spawns.dm b/code/controllers/subsystem/minimum_spawns.dm
index caab2b1949..b9d19b6cd2 100644
--- a/code/controllers/subsystem/minimum_spawns.dm
+++ b/code/controllers/subsystem/minimum_spawns.dm
@@ -1,7 +1,7 @@
SUBSYSTEM_DEF(min_spawns)
name = "Minimum Spawns" /// this hot steaming pile of garbage makes sure theres a minimum of tendrils scattered around
init_order = INIT_ORDER_DEFAULT
- flags = SS_BACKGROUND | SS_NO_FIRE | SS_ALWAYS_SHOW_STAT
+ flags = SS_BACKGROUND | SS_NO_FIRE
wait = 2
var/where_we_droppin_boys_iterations = 0
var/snaxi_snowflake_check = FALSE
@@ -71,7 +71,7 @@ GLOBAL_LIST_INIT(minimum_snow_under_spawns, list(
continue
if(typesof(/turf/open/lava) in orange(9, TT))
continue
- valid_mining_turfs_2.Add(TT)
+ valid_mining_turfs_2.Add(TT)
else
for(var/z_level in SSmapping.levels_by_trait(ZTRAIT_LAVA_RUINS))
for(var/turf/TT in Z_TURFS(z_level))
@@ -103,14 +103,14 @@ GLOBAL_LIST_INIT(minimum_snow_under_spawns, list(
for(var/mob/living/simple_animal/hostile/megafauna/H in urange(70,RT)) //prevents mob clumps
if((istype(MS_tospawn, /mob/living/simple_animal/hostile/megafauna)) && get_dist(RT, H) <= 70)
active_spawns.Add(MS_tospawn)
- continue //let's try not to dump megas too close to each other?
+ continue //let's try not to dump megas too close to each other?
if((istype(MS_tospawn, /obj/structure/spawner)) && get_dist(RT, H) <= 40)
active_spawns.Add(MS_tospawn)
continue //let's at least /try/ to space these out?
for(var/obj/structure/spawner/LT in urange(70,RT)) //prevents tendril/mega clumps
if((istype(MS_tospawn, /mob/living/simple_animal/hostile/megafauna)) && get_dist(RT, LT) <= 70)
active_spawns.Add(MS_tospawn)
- continue //let's try not to dump megas too close to each other?
+ continue //let's try not to dump megas too close to each other?
if((istype(MS_tospawn, /obj/structure/spawner)) && get_dist(RT, LT) <= 40)
active_spawns.Add(MS_tospawn)
continue //let's at least /try/ to space these out?
@@ -127,7 +127,7 @@ GLOBAL_LIST_INIT(minimum_snow_under_spawns, list(
for(var/mob/living/simple_animal/hostile/H in urange(70,RT2)) //prevents mob clumps
if((istype(MS2_tospawn, /mob/living/simple_animal/hostile/megafauna) || ismegafauna(H)) && get_dist(RT2, H) <= 70)
active_spawns_2.Add(MS2_tospawn)
- continue //let's try not to dump megas too close to each other?
+ continue //let's try not to dump megas too close to each other?
if((istype(MS2_tospawn, /obj/structure/spawner)) && get_dist(RT2, H) <= 40)
active_spawns_2.Add(MS2_tospawn)
continue //let's at least /try/ to space these out?
diff --git a/code/controllers/subsystem/minor_mapping.dm b/code/controllers/subsystem/minor_mapping.dm
index bd950e453e..d6cbf99f97 100644
--- a/code/controllers/subsystem/minor_mapping.dm
+++ b/code/controllers/subsystem/minor_mapping.dm
@@ -1,3 +1,5 @@
+#define PROB_MOUSE_SPAWN 98
+
SUBSYSTEM_DEF(minor_mapping)
name = "Minor Mapping"
init_order = INIT_ORDER_MINOR_MAPPING
@@ -5,29 +7,43 @@ SUBSYSTEM_DEF(minor_mapping)
/datum/controller/subsystem/minor_mapping/Initialize(timeofday)
trigger_migration(CONFIG_GET(number/mice_roundstart))
+ // place_satchels()
return ..()
/datum/controller/subsystem/minor_mapping/proc/trigger_migration(num_mice=10)
var/list/exposed_wires = find_exposed_wires()
- var/mob/living/simple_animal/mouse/M
+ var/mob/living/simple_animal/mouse/mouse
var/turf/proposed_turf
while((num_mice > 0) && exposed_wires.len)
proposed_turf = pick_n_take(exposed_wires)
- if(!M)
- M = new(proposed_turf)
- else
- M.forceMove(proposed_turf)
- if(M.environment_is_safe())
- num_mice -= 1
- M = null
+ if(prob(PROB_MOUSE_SPAWN))
+ if(!mouse)
+ mouse = new(proposed_turf)
+ else
+ mouse.forceMove(proposed_turf)
+ // else
+ // mouse = new /mob/living/simple_animal/hostile/regalrat/controlled(proposed_turf)
+ if(mouse.environment_is_safe())
+ num_mice -= 1
+ mouse = null
+
+// /datum/controller/subsystem/minor_mapping/proc/place_satchels(amount=10)
+// var/list/turfs = find_satchel_suitable_turfs()
+
+// while(turfs.len && amount > 0)
+// var/turf/T = pick_n_take(turfs)
+// var/obj/item/storage/backpack/satchel/flat/F = new(T)
+
+// SEND_SIGNAL(F, COMSIG_OBJ_HIDE, T.intact)
+// amount--
/proc/find_exposed_wires()
var/list/exposed_wires = list()
- exposed_wires.Cut()
+
var/list/all_turfs
- for (var/z in SSmapping.levels_by_trait(ZTRAIT_STATION))
+ for(var/z in SSmapping.levels_by_trait(ZTRAIT_STATION))
all_turfs += block(locate(1,1,z), locate(world.maxx,world.maxy,z))
for(var/turf/open/floor/plating/T in all_turfs)
if(is_blocked_turf(T))
@@ -36,3 +52,15 @@ SUBSYSTEM_DEF(minor_mapping)
exposed_wires += T
return shuffle(exposed_wires)
+
+// /proc/find_satchel_suitable_turfs()
+// var/list/suitable = list()
+
+// for(var/z in SSmapping.levels_by_trait(ZTRAIT_STATION))
+// for(var/t in block(locate(1,1,z), locate(world.maxx,world.maxy,z)))
+// if(isfloorturf(t) && !isplatingturf(t))
+// suitable += t
+
+// return shuffle(suitable)
+
+#undef PROB_MOUSE_SPAWN
diff --git a/code/controllers/subsystem/overlays.dm b/code/controllers/subsystem/overlays.dm
index b42a1e6b7e..74b60783c3 100644
--- a/code/controllers/subsystem/overlays.dm
+++ b/code/controllers/subsystem/overlays.dm
@@ -48,9 +48,16 @@ SUBSYSTEM_DEF(overlays)
for (var/thing in queue)
count++
if(thing)
- STAT_START_STOPWATCH
var/atom/A = thing
+ if(A.overlays.len >= MAX_ATOM_OVERLAYS)
+ //Break it real GOOD
+ stack_trace("Too many overlays on [A.type] - [A.overlays.len], refusing to update and cutting")
+ A.overlays.Cut()
+ continue
+ STAT_START_STOPWATCH
COMPILE_OVERLAYS(A)
+ UNSETEMPTY(A.add_overlays)
+ UNSETEMPTY(A.remove_overlays)
STAT_STOP_STOPWATCH
STAT_LOG_ENTRY(stats, A.type)
if(mc_check)
@@ -117,9 +124,8 @@ SUBSYSTEM_DEF(overlays)
#define QUEUE_FOR_COMPILE flags_1 |= OVERLAY_QUEUED_1; SSoverlays.queue += src;
/atom/proc/cut_overlays()
LAZYINITLIST(remove_overlays)
- LAZYINITLIST(add_overlays)
remove_overlays = overlays.Copy()
- add_overlays.Cut()
+ add_overlays = null
//If not already queued for work and there are overlays to remove
if(NOT_QUEUED_ALREADY && remove_overlays.len)
@@ -129,7 +135,7 @@ SUBSYSTEM_DEF(overlays)
if(!overlays)
return
overlays = build_appearance_list(overlays)
- LAZYINITLIST(add_overlays) //always initialized after this point
+ LAZYINITLIST(add_overlays)
LAZYINITLIST(remove_overlays)
var/a_len = add_overlays.len
var/r_len = remove_overlays.len
@@ -140,8 +146,9 @@ SUBSYSTEM_DEF(overlays)
var/fr_len = remove_overlays.len
//If not already queued and there is work to be done
- if(NOT_QUEUED_ALREADY && (fa_len != a_len || fr_len != r_len))
+ if(NOT_QUEUED_ALREADY && (fa_len != a_len || fr_len != r_len ))
QUEUE_FOR_COMPILE
+ UNSETEMPTY(add_overlays)
/atom/proc/add_overlay(list/overlays)
if(!overlays)
diff --git a/code/controllers/subsystem/parallax.dm b/code/controllers/subsystem/parallax.dm
index 64299fda38..7096c667e1 100644
--- a/code/controllers/subsystem/parallax.dm
+++ b/code/controllers/subsystem/parallax.dm
@@ -7,13 +7,21 @@ SUBSYSTEM_DEF(parallax)
var/list/currentrun
var/planet_x_offset = 128
var/planet_y_offset = 128
+ var/random_layer
+ var/random_parallax_color
-/datum/controller/subsystem/parallax/Initialize(timeofday)
+
+//These are cached per client so needs to be done asap so people joining at roundstart do not miss these.
+/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_parallax_color = pick(COLOR_TEAL, COLOR_GREEN, COLOR_YELLOW, COLOR_CYAN, COLOR_ORANGE, COLOR_PURPLE)//Special color for random_layer1. Has to be done here so everyone sees the same color. [COLOR_SILVER]
planet_y_offset = rand(100, 160)
planet_x_offset = rand(100, 160)
-/datum/controller/subsystem/parallax/fire(resumed = 0)
+
+/datum/controller/subsystem/parallax/fire(resumed = FALSE)
if (!resumed)
src.currentrun = GLOB.clients.Copy()
@@ -21,24 +29,27 @@ SUBSYSTEM_DEF(parallax)
var/list/currentrun = src.currentrun
while(length(currentrun))
- var/client/C = currentrun[currentrun.len]
+ var/client/processing_client = currentrun[currentrun.len]
currentrun.len--
- if (!C || !C.eye)
+ if (QDELETED(processing_client) || !processing_client.eye)
if (MC_TICK_CHECK)
return
continue
- var/atom/movable/A = C.eye
- if(!istype(A))
- continue
- for (A; isloc(A.loc) && !isturf(A.loc); A = A.loc);
- if(A != C.movingmob)
- if(C.movingmob != null)
- C.movingmob.client_mobs_in_contents -= C.mob
- UNSETEMPTY(C.movingmob.client_mobs_in_contents)
- LAZYINITLIST(A.client_mobs_in_contents)
- A.client_mobs_in_contents += C.mob
- C.movingmob = A
+ var/atom/movable/movable_eye = processing_client.eye
+ if(!istype(movable_eye))
+ continue
+
+ for (movable_eye; isloc(movable_eye.loc) && !isturf(movable_eye.loc); movable_eye = movable_eye.loc);
+
+ if(movable_eye == processing_client.movingmob)
+ if (MC_TICK_CHECK)
+ return
+ continue
+ if(!isnull(processing_client.movingmob))
+ LAZYREMOVE(processing_client.movingmob.client_mobs_in_contents, processing_client.mob)
+ LAZYADD(movable_eye.client_mobs_in_contents, processing_client.mob)
+ processing_client.movingmob = movable_eye
if (MC_TICK_CHECK)
return
currentrun = null
diff --git a/code/controllers/subsystem/pathfinder.dm b/code/controllers/subsystem/pathfinder.dm
index 8e1cf946ae..ccbea79306 100644
--- a/code/controllers/subsystem/pathfinder.dm
+++ b/code/controllers/subsystem/pathfinder.dm
@@ -18,7 +18,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/_persistence.dm b/code/controllers/subsystem/persistence/_persistence.dm
index 741d1dd72a..d494561d0f 100644
--- a/code/controllers/subsystem/persistence/_persistence.dm
+++ b/code/controllers/subsystem/persistence/_persistence.dm
@@ -58,6 +58,7 @@ SUBSYSTEM_DEF(persistence)
if(CONFIG_GET(flag/use_antag_rep))
LoadAntagReputation()
LoadRandomizedRecipes()
+ LoadPaintings()
/**
* Saves persistent data relevant to the server: Configurations, past gamemodes, votes, antag rep, etc
@@ -348,3 +349,15 @@ SUBSYSTEM_DEF(persistence)
if(!ending_human.client)
return
ending_human.client.prefs.save_character()
+
+/datum/controller/subsystem/persistence/proc/SaveTCGCards()
+ for(var/i in GLOB.joined_player_list)
+ var/mob/living/carbon/human/ending_human = get_mob_by_ckey(i)
+ if(!istype(ending_human) || !ending_human.mind || !ending_human.client || !ending_human.client.prefs || !ending_human.client.prefs.tcg_cards)
+ continue
+
+ var/mob/living/carbon/human/original_human = ending_human.mind.original_character
+ if(!original_human || original_human.stat == DEAD || !(original_human == ending_human))
+ continue
+
+ ending_human.SaveTCGCards()
diff --git a/code/controllers/subsystem/persistence/recent_votes_etc.dm b/code/controllers/subsystem/persistence/recent_votes_etc.dm
index f1b902d6ab..87f1ec0d4f 100644
--- a/code/controllers/subsystem/persistence/recent_votes_etc.dm
+++ b/code/controllers/subsystem/persistence/recent_votes_etc.dm
@@ -3,6 +3,7 @@
*/
/datum/controller/subsystem/persistence
var/list/saved_modes = list(1,2,3)
+ var/list/saved_chaos = list(5,5,5)
var/list/saved_dynamic_rules = list(list(),list(),list())
var/list/saved_storytellers = list("foo","bar","baz")
var/list/average_dynamic_threat = 50
@@ -20,6 +21,7 @@
/datum/controller/subsystem/persistence/LoadServerPersistence()
. = ..()
LoadRecentModes()
+ LoadRecentChaos()
LoadRecentStorytellers()
LoadRecentRulesets()
LoadRecentMaps()
@@ -33,6 +35,14 @@
file_data["data"] = saved_modes
fdel(json_file)
WRITE_FILE(json_file, json_encode(file_data))
+ saved_chaos[3] = saved_chaos[2]
+ saved_chaos[2] = saved_chaos[1]
+ saved_chaos[1] = SSticker.mode.get_chaos()
+ json_file = file("data/RecentChaos.json")
+ file_data = list()
+ file_data["data"] = saved_chaos
+ fdel(json_file)
+ WRITE_FILE(json_file, json_encode(file_data))
/datum/controller/subsystem/persistence/proc/CollectStoryteller(var/datum/game_mode/dynamic/mode)
saved_storytellers.len = 3
@@ -76,6 +86,15 @@
return
saved_modes = json["data"]
+/datum/controller/subsystem/persistence/proc/LoadRecentChaos()
+ var/json_file = file("data/RecentChaos.json")
+ if(!fexists(json_file))
+ return
+ var/list/json = json_decode(file2text(json_file))
+ if(!json)
+ return
+ saved_chaos = json["data"]
+
/datum/controller/subsystem/persistence/proc/LoadRecentRulesets()
var/json_file = file("data/RecentRulesets.json")
if(!fexists(json_file))
@@ -105,3 +124,9 @@
if(!json)
return
saved_maps = json["maps"]
+
+/datum/controller/subsystem/persistence/proc/get_recent_chaos()
+ var/sum = 0
+ for(var/n in saved_chaos)
+ sum += n
+ return sum/length(saved_chaos)
diff --git a/code/controllers/subsystem/processing/fastprocess.dm b/code/controllers/subsystem/processing/fastprocess.dm
index 9622e02146..1b30ca44c2 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/nanites.dm b/code/controllers/subsystem/processing/nanites.dm
index c34e7f7806..8a55491f5f 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 26021fb267..3566e8a4dc 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/processing.dm b/code/controllers/subsystem/processing/processing.dm
index 637b04993d..5cefd3a148 100644
--- a/code/controllers/subsystem/processing/processing.dm
+++ b/code/controllers/subsystem/processing/processing.dm
@@ -1,10 +1,10 @@
-//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()
@@ -14,9 +14,10 @@ SUBSYSTEM_DEF(processing)
msg = "[stat_tag]:[length(processing)]"
return ..()
-/datum/controller/subsystem/processing/fire(resumed = 0)
+/datum/controller/subsystem/processing/fire(resumed = FALSE)
if (!resumed)
currentrun = processing.Copy()
+ var/delta_time = (flags & SS_TICKER)? (wait * world.tick_lag * 0.1) : (wait * 0.1)
//cache for sanic speed (lists are references anyways)
var/list/current_run = currentrun
@@ -25,12 +26,26 @@ SUBSYSTEM_DEF(processing)
current_run.len--
if(QDELETED(thing))
processing -= thing
- else if(thing.process(wait) == PROCESS_KILL)
+ else if(thing.process(delta_time) == 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 c57bac5526..74cd53b0ae 100644
--- a/code/controllers/subsystem/processing/quirks.dm
+++ b/code/controllers/subsystem/processing/quirks.dm
@@ -5,8 +5,8 @@ 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_names_by_path = list()
diff --git a/code/controllers/subsystem/processing/weather.dm b/code/controllers/subsystem/processing/weather.dm
index ca067953cc..4035149ef2 100644
--- a/code/controllers/subsystem/processing/weather.dm
+++ b/code/controllers/subsystem/processing/weather.dm
@@ -70,3 +70,8 @@ PROCESSING_SUBSYSTEM_DEF(weather)
A = W
break
return A
+
+/datum/controller/subsystem/processing/weather/proc/get_weather_by_type(datum/weather/weather_datum_type)
+ for(var/V in processing)
+ if(istype(V,weather_datum_type))
+ return V
diff --git a/code/controllers/subsystem/profiler.dm b/code/controllers/subsystem/profiler.dm
index 81fa77dc6c..7533e9663f 100644
--- a/code/controllers/subsystem/profiler.dm
+++ b/code/controllers/subsystem/profiler.dm
@@ -18,7 +18,7 @@ SUBSYSTEM_DEF(profiler)
if(CONFIG_GET(flag/auto_profile))
StartProfiling()
else
- StopProfiling() //Stop the early start from world/New
+ StopProfiling() //Stop the early start profiler
return ..()
/datum/controller/subsystem/profiler/fire()
@@ -31,12 +31,23 @@ SUBSYSTEM_DEF(profiler)
return ..()
/datum/controller/subsystem/profiler/proc/StartProfiling()
+#if DM_BUILD < 1506
+ stack_trace("Auto profiling unsupported on this byond version")
+ CONFIG_SET(flag/auto_profile, FALSE)
+#else
world.Profile(PROFILE_START)
+#endif
/datum/controller/subsystem/profiler/proc/StopProfiling()
+#if DM_BUILD >= 1506
world.Profile(PROFILE_STOP)
+#endif
/datum/controller/subsystem/profiler/proc/DumpFile()
+#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")
fetch_cost = MC_AVERAGE(fetch_cost, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer))
@@ -49,3 +60,4 @@ SUBSYSTEM_DEF(profiler)
timer = TICK_USAGE_REAL
WRITE_FILE(json_file, current_profile_data)
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 f29fe72e80..2d764d84dd 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()
@@ -13,5 +14,5 @@ PROCESSING_SUBSYSTEM_DEF(radiation)
warned_atoms[ref] = TRUE
var/atom/master = contamination.parent
SSblackbox.record_feedback("tally", "contaminated", 1, master.type)
- var/msg = "has become contamintaed with enough radiation to contaminate other objects. || Source: [contamination.source] || Strength: [contamination.strength]"
+ var/msg = "has become contaminated with enough radiation to contaminate other objects. || Source: [contamination.source] || Strength: [contamination.strength]"
master.investigate_log(msg, INVESTIGATE_RADIATION)
diff --git a/code/controllers/subsystem/runechat.dm b/code/controllers/subsystem/runechat.dm
index ec296e7d32..9bd665e5ee 100644
--- a/code/controllers/subsystem/runechat.dm
+++ b/code/controllers/subsystem/runechat.dm
@@ -6,15 +6,15 @@
#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. 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.
- */
+ * # Runechat Subsystem
+ *
+ * Maintains a timer-like system to handle destruction of runechat messages. 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
@@ -131,14 +131,14 @@ SUBSYSTEM_DEF(runechat)
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
- */
+ * 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
@@ -169,7 +169,7 @@ SUBSYSTEM_DEF(runechat)
// 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)
+ 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
@@ -194,8 +194,8 @@ SUBSYSTEM_DEF(runechat)
/**
- * Removes this chatmessage datum from the runechat subsystem
- */
+ * 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)
diff --git a/code/controllers/subsystem/server_maint.dm b/code/controllers/subsystem/server_maint.dm
index 2427fbd277..7eb81003f1 100644
--- a/code/controllers/subsystem/server_maint.dm
+++ b/code/controllers/subsystem/server_maint.dm
@@ -56,12 +56,13 @@ SUBSYSTEM_DEF(server_maint)
for(var/I in currentrun)
var/client/C = I
//handle kicking inactive players
- if(round_started && kick_inactive && C.is_afk(afk_period))
+ if(round_started && kick_inactive && !C.holder && C.is_afk(afk_period))
var/cmob = C.mob
- if(!(isobserver(cmob) || (isdead(cmob) && C.holder)))
+ if (!isnewplayer(cmob) || !SSticker.queued_players.Find(cmob))
log_access("AFK: [key_name(C)]")
- to_chat(C, "You have been inactive for more than [DisplayTimeText(afk_period)] and have been disconnected.")
- qdel(C)
+ to_chat(C, "You have been inactive for more than [DisplayTimeText(afk_period)] and have been disconnected.
You may reconnect via the button in the file menu or by clicking here to reconnect.")
+ QDEL_IN(C, 1) //to ensure they get our message before getting disconnected
+ continue
if (!(!C || world.time - C.connection_time < PING_BUFFER_TIME || C.inactivity >= (wait-1)))
winset(C, null, "command=.update_ping+[world.time+world.tick_lag*TICK_USAGE_REAL/100]")
@@ -83,4 +84,15 @@ SUBSYSTEM_DEF(server_maint)
if(tgsversion)
SSblackbox.record_feedback("text", "server_tools", 1, tgsversion.raw_parameter)
+
+/datum/controller/subsystem/server_maint/proc/UpdateHubStatus()
+ // if(!CONFIG_GET(flag/hub) || !CONFIG_GET(number/max_hub_pop))
+ // return FALSE //no point, hub / auto hub controls are disabled
+
+ // var/max_pop = CONFIG_GET(number/max_hub_pop)
+
+ // if(GLOB.clients.len > max_pop)
+ // world.update_hub_visibility(FALSE)
+ // else
+ // world.update_hub_visibility(TRUE)
#undef PING_BUFFER_TIME
diff --git a/code/controllers/subsystem/shuttle.dm b/code/controllers/subsystem/shuttle.dm
index b74f1d46d3..889fdf35a1 100644
--- a/code/controllers/subsystem/shuttle.dm
+++ b/code/controllers/subsystem/shuttle.dm
@@ -12,6 +12,10 @@ SUBSYSTEM_DEF(shuttle)
var/list/beacons = list()
var/list/transit = list()
+ //Now it only for ID generation
+ var/list/assoc_mobile = list()
+ var/list/assoc_stationary = list()
+
var/list/transit_requesters = list()
var/list/transit_request_failures = list()
@@ -26,6 +30,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
@@ -65,6 +70,8 @@ SUBSYSTEM_DEF(shuttle)
var/datum/turf_reservation/preview_reservation
+ var/shuttle_loading
+
/datum/controller/subsystem/shuttle/Initialize(timeofday)
ordernum = rand(1, 9000)
@@ -134,7 +141,7 @@ SUBSYSTEM_DEF(shuttle)
break
/datum/controller/subsystem/shuttle/proc/CheckAutoEvac()
- if(emergencyNoEscape || emergencyNoRecall || !emergency || !SSticker.HasRoundStarted())
+ if(emergencyNoEscape || adminEmergencyNoRecall || emergencyNoRecall || !emergency || !SSticker.HasRoundStarted())
return
var/threshold = CONFIG_GET(number/emergency_shuttle_autocall_threshold)
@@ -179,31 +186,26 @@ SUBSYSTEM_DEF(shuttle)
return S
WARNING("couldn't find dock with id: [id]")
+/// Check if we can call the evac shuttle.
+/// Returns TRUE if we can. Otherwise, returns a string detailing the problem.
/datum/controller/subsystem/shuttle/proc/canEvac(mob/user)
var/srd = CONFIG_GET(number/shuttle_refuel_delay)
if(world.time - SSticker.round_start_time < srd)
- to_chat(user, "The emergency shuttle is refueling. Please wait [DisplayTimeText(srd - (world.time - SSticker.round_start_time))] before trying again.")
- return FALSE
+ return "The emergency shuttle is refueling. Please wait [DisplayTimeText(srd - (world.time - SSticker.round_start_time))] before attempting to call."
switch(emergency.mode)
if(SHUTTLE_RECALL)
- to_chat(user, "The emergency shuttle may not be called while returning to CentCom.")
- return FALSE
+ return "The emergency shuttle may not be called while returning to CentCom."
if(SHUTTLE_CALL)
- to_chat(user, "The emergency shuttle is already on its way.")
- return FALSE
+ return "The emergency shuttle is already on its way."
if(SHUTTLE_DOCKED)
- to_chat(user, "The emergency shuttle is already here.")
- return FALSE
+ return "The emergency shuttle is already here."
if(SHUTTLE_IGNITING)
- to_chat(user, "The emergency shuttle is firing its engines to leave.")
- return FALSE
+ return "The emergency shuttle is firing its engines to leave."
if(SHUTTLE_ESCAPE)
- to_chat(user, "The emergency shuttle is moving away to a safe distance.")
- return FALSE
+ return "The emergency shuttle is moving away to a safe distance."
if(SHUTTLE_STRANDED)
- to_chat(user, "The emergency shuttle has been disabled by CentCom.")
- return FALSE
+ return "The emergency shuttle has been disabled by CentCom."
return TRUE
@@ -221,7 +223,9 @@ SUBSYSTEM_DEF(shuttle)
Good luck.")
emergency = backup_shuttle
- if(!canEvac(user))
+ var/can_evac_or_fail_reason = SSshuttle.canEvac(user)
+ if(can_evac_or_fail_reason != TRUE)
+ to_chat(user, "[can_evac_or_fail_reason]")
return
call_reason = trim(html_encode(call_reason))
@@ -250,10 +254,11 @@ SUBSYSTEM_DEF(shuttle)
var/area/A = get_area(user)
log_shuttle("[key_name(user)] has called the emergency shuttle.")
- deadchat_broadcast(" has called the shuttle at [A.name].", "[user.real_name]", user)
+ deadchat_broadcast(" has called the shuttle at [A.name].", "[user.real_name]", user) //, message_type=DEADCHAT_ANNOUNCEMENT)
if(call_reason)
SSblackbox.record_feedback("text", "shuttle_reason", 1, "[call_reason]")
log_shuttle("Shuttle call reason: [call_reason]")
+ SSticker.emergency_reason = call_reason
message_admins("[ADMIN_LOOKUPFLW(user)] has called the shuttle. (TRIGGER CENTCOM RECALL)")
/datum/controller/subsystem/shuttle/proc/centcom_recall(old_timer, admiral_message)
@@ -288,7 +293,7 @@ SUBSYSTEM_DEF(shuttle)
emergency.cancel(get_area(user))
log_shuttle("[key_name(user)] has recalled the shuttle.")
message_admins("[ADMIN_LOOKUPFLW(user)] has recalled the shuttle.")
- deadchat_broadcast(" has recalled the shuttle from [get_area_name(user, TRUE)].", "[user.real_name]", user)
+ deadchat_broadcast(" has recalled the shuttle from [get_area_name(user, TRUE)].", "[user.real_name]", user) //, message_type=DEADCHAT_ANNOUNCEMENT)
return 1
/datum/controller/subsystem/shuttle/proc/canRecall()
@@ -314,7 +319,7 @@ SUBSYSTEM_DEF(shuttle)
if (!SSticker.IsRoundInProgress())
return
- var/callShuttle = 1
+ var/callShuttle = TRUE
for(var/thing in GLOB.shuttle_caller_list)
if(isAI(thing))
@@ -330,7 +335,7 @@ SUBSYSTEM_DEF(shuttle)
var/turf/T = get_turf(thing)
if(T && is_station_level(T.z))
- callShuttle = 0
+ callShuttle = FALSE
break
if(callShuttle)
@@ -406,7 +411,7 @@ SUBSYSTEM_DEF(shuttle)
else
if(M.initiate_docking(getDock(destination)) != DOCKING_SUCCESS)
return 2
- return 0 //dock successful
+ return 0 //dock successful
/datum/controller/subsystem/shuttle/proc/moveShuttle(shuttleId, dockId, timed)
@@ -664,7 +669,7 @@ SUBSYSTEM_DEF(shuttle)
emergencyNoRecall = TRUE
endvote_passed = TRUE
-/datum/controller/subsystem/shuttle/proc/action_load(datum/map_template/shuttle/loading_template, obj/docking_port/stationary/destination_port)
+/datum/controller/subsystem/shuttle/proc/action_load(datum/map_template/shuttle/loading_template, obj/docking_port/stationary/destination_port, replace = FALSE)
// Check for an existing preview
if(preview_shuttle && (loading_template != preview_template))
preview_shuttle.jumpToNullSpace()
@@ -673,8 +678,8 @@ SUBSYSTEM_DEF(shuttle)
QDEL_NULL(preview_reservation)
if(!preview_shuttle)
- if(load_template(loading_template))
- preview_shuttle.linkup(loading_template, destination_port)
+ load_template(loading_template)
+ // preview_shuttle.linkup(loading_template, destination_port)
preview_template = loading_template
// get the existing shuttle information, if any
@@ -684,7 +689,7 @@ SUBSYSTEM_DEF(shuttle)
if(istype(destination_port))
D = destination_port
- else if(existing_shuttle)
+ else if(existing_shuttle && replace)
timer = existing_shuttle.timer
mode = existing_shuttle.mode
D = existing_shuttle.get_docked()
@@ -703,11 +708,12 @@ SUBSYSTEM_DEF(shuttle)
WARNING("Template shuttle [preview_shuttle] cannot dock at [D] ([result]).")
return
- if(existing_shuttle)
+ if(existing_shuttle && replace)
existing_shuttle.jumpToNullSpace()
var/list/force_memory = preview_shuttle.movement_force
preview_shuttle.movement_force = list("KNOCKDOWN" = 0, "THROW" = 0)
+ preview_shuttle.mode = SHUTTLE_PREARRIVAL//No idle shuttle moving. Transit dock get removed if shuttle moves too long.
preview_shuttle.initiate_docking(D)
preview_shuttle.movement_force = force_memory
@@ -718,7 +724,7 @@ SUBSYSTEM_DEF(shuttle)
preview_shuttle.timer = timer
preview_shuttle.mode = mode
- preview_shuttle.register()
+ preview_shuttle.register(replace)
// TODO indicate to the user that success happened, rather than just
// blanking the modification tab
@@ -848,7 +854,8 @@ SUBSYSTEM_DEF(shuttle)
return data
/datum/controller/subsystem/shuttle/ui_act(action, params)
- if(..())
+ . = ..()
+ if(.)
return
var/mob/user = usr
@@ -891,22 +898,10 @@ SUBSYSTEM_DEF(shuttle)
SSblackbox.record_feedback("text", "shuttle_manipulator", 1, "[M.name]")
break
- if("preview")
- if(S)
- . = TRUE
- unload_preview()
- load_template(S)
- if(preview_shuttle)
- preview_template = S
- user.forceMove(get_turf(preview_shuttle))
if("load")
- if(existing_shuttle == backup_shuttle)
- // TODO make the load button disabled
- WARNING("The shuttle that the selected shuttle will replace \
- is the backup shuttle. Backup shuttle is required to be \
- intact for round sanity.")
- else if(S)
+ if(S && !shuttle_loading)
. = TRUE
+ shuttle_loading = TRUE
// If successful, returns the mobile docking port
var/obj/docking_port/mobile/mdp = action_load(S)
if(mdp)
@@ -914,3 +909,38 @@ SUBSYSTEM_DEF(shuttle)
message_admins("[key_name_admin(usr)] loaded [mdp] with the shuttle manipulator.")
log_admin("[key_name(usr)] loaded [mdp] with the shuttle manipulator.")
SSblackbox.record_feedback("text", "shuttle_manipulator", 1, "[mdp.name]")
+ shuttle_loading = FALSE
+
+ if("preview")
+ //if(preview_shuttle && (loading_template != preview_template))
+ if(S && !shuttle_loading)
+ . = TRUE
+ shuttle_loading = TRUE
+ unload_preview()
+ load_template(S)
+ if(preview_shuttle)
+ preview_template = S
+ user.forceMove(get_turf(preview_shuttle))
+ shuttle_loading = FALSE
+
+ if("replace")
+ if(existing_shuttle == backup_shuttle)
+ // TODO make the load button disabled
+ WARNING("The shuttle that the selected shuttle will replace \
+ is the backup shuttle. Backup shuttle is required to be \
+ intact for round sanity.")
+ else if(S && !shuttle_loading)
+ . = TRUE
+ shuttle_loading = TRUE
+ // If successful, returns the mobile docking port
+ var/obj/docking_port/mobile/mdp = action_load(S, replace = TRUE)
+ if(mdp)
+ user.forceMove(get_turf(mdp))
+ message_admins("[key_name_admin(usr)] load/replaced [mdp] with the shuttle manipulator.")
+ log_admin("[key_name(usr)] load/replaced [mdp] with the shuttle manipulator.")
+ SSblackbox.record_feedback("text", "shuttle_manipulator", 1, "[mdp.name]")
+ shuttle_loading = FALSE
+ if(emergency == mdp) //you just changed the emergency shuttle, there are events in game + captains that can change your snowflake choice.
+ var/set_purchase = alert(usr, "Do you want to also disable shuttle purchases/random events that would change the shuttle?", "Butthurt Admin Prevention", "Yes, disable purchases/events", "No, I want to possibly get owned")
+ if(set_purchase == "Yes, disable purchases/events")
+ SSshuttle.shuttle_purchased = SHUTTLEPURCHASE_FORCED
diff --git a/code/controllers/subsystem/sounds.dm b/code/controllers/subsystem/sounds.dm
index 5e7c5e6545..fa9ba3c472 100644
--- a/code/controllers/subsystem/sounds.dm
+++ b/code/controllers/subsystem/sounds.dm
@@ -5,34 +5,48 @@ SUBSYSTEM_DEF(sounds)
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 = list()
+ /// 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 = list()
- /// List of all available channels with associations set to TRUE for fast lookups/allocation.
- var/list/available_channels
+ 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()
- available_channels = list()
+ channel_list = list()
+ reserved_channels = list()
+ using_channels = list()
+ using_channels_by_datum = list()
for(var/i in 1 to using_channels_max)
- available_channels[num2text(i)] = TRUE
+ 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)
- channel = num2text(channel)
- var/using = using_channels[channel]
- using_channels -= channel
- if(using)
+ 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
- available_channels[channel] = TRUE
+ free_channel(channel)
/// Frees all the channels a datum is using.
/datum/controller/subsystem/sounds/proc/free_datum_channels(datum/D)
@@ -40,8 +54,8 @@ SUBSYSTEM_DEF(sounds)
if(!L)
return
for(var/channel in L)
- using_channels -= channel
- available_channels[channel] = TRUE
+ using_channels -= num2text(channel)
+ free_channel(channel)
using_channels_by_datum -= D
/// Frees all datumless channels
@@ -50,42 +64,72 @@ SUBSYSTEM_DEF(sounds)
/// 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()
- var/channel = random_available_channel_text()
- if(!channel) //oh no..
+ . = reserve_channel()
+ if(!.) //oh no..
return FALSE
- available_channels -= channel
- using_channels[channel] = DATUMLESS
+ var/text_channel = num2text(.)
+ using_channels[text_channel] = DATUMLESS
LAZYINITLIST(using_channels_by_datum[DATUMLESS])
- using_channels_by_datum[DATUMLESS] += channel
- return text2num(channel)
+ 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.")
- var/channel = random_available_channel_text()
- if(!channel)
+ .= reserve_channel()
+ if(!.)
return FALSE
- available_channels -= channel
- using_channels[channel] = D
+ var/text_channel = num2text(.)
+ using_channels[text_channel] = D
LAZYINITLIST(using_channels_by_datum[D])
- using_channels_by_datum[D] += channel
- return text2num(channel)
+ 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()
- return pick(available_channels)
+ 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()
- return text2num(pick(available_channels))
-
-/// If a channel is available
-/datum/controller/subsystem/sounds/proc/is_channel_available(channel)
- return available_channels[num2text(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(available_channels)
+ return length(channel_list) - random_channels_min
#undef DATUMLESS
diff --git a/code/controllers/subsystem/spacedrift.dm b/code/controllers/subsystem/spacedrift.dm
index c3261df304..e84a70a45f 100644
--- a/code/controllers/subsystem/spacedrift.dm
+++ b/code/controllers/subsystem/spacedrift.dm
@@ -13,7 +13,7 @@ SUBSYSTEM_DEF(spacedrift)
return ..()
-/datum/controller/subsystem/spacedrift/fire(resumed = 0)
+/datum/controller/subsystem/spacedrift/fire(resumed = FALSE)
if (!resumed)
src.currentrun = processing.Copy()
@@ -47,6 +47,7 @@ SUBSYSTEM_DEF(spacedrift)
var/old_dir = AM.dir
var/old_loc = AM.loc
AM.inertia_moving = TRUE
+ AM.set_glide_size(DELAY_TO_GLIDE_SIZE(AM.inertia_move_delay), FALSE)
step(AM, AM.inertia_dir)
AM.inertia_moving = FALSE
AM.inertia_next_move = world.time + AM.inertia_move_delay
diff --git a/code/controllers/subsystem/statpanel.dm b/code/controllers/subsystem/statpanel.dm
index 13e9ff50a2..d0d5579611 100644
--- a/code/controllers/subsystem/statpanel.dm
+++ b/code/controllers/subsystem/statpanel.dm
@@ -203,3 +203,4 @@ SUBSYSTEM_DEF(statpanels)
set hidden = TRUE
statbrowser_ready = TRUE
+ init_verbs()
diff --git a/code/controllers/subsystem/stickyban.dm b/code/controllers/subsystem/stickyban.dm
index 189efa99fe..c61ea9943e 100644
--- a/code/controllers/subsystem/stickyban.dm
+++ b/code/controllers/subsystem/stickyban.dm
@@ -1,34 +1,216 @@
SUBSYSTEM_DEF(stickyban)
- name = "Sticky Ban"
+ name = "PRISM"
init_order = INIT_ORDER_STICKY_BAN
flags = SS_NO_FIRE
var/list/cache = list()
+ var/list/dbcache = list()
+ var/list/confirmed_exempt = list()
+ var/dbcacheexpire = 0
+
/datum/controller/subsystem/stickyban/Initialize(timeofday)
- var/list/bannedkeys = world.GetConfig("ban")
+ if (length(GLOB.stickybanadminexemptions))
+ restore_stickybans()
+ var/list/bannedkeys = sticky_banned_ckeys()
//sanitize the sticky ban list
+
+ //delete db bans that no longer exist in the database and add new legacy bans to the database
+ if (SSdbcore.Connect() || length(SSstickyban.dbcache))
+ if (length(GLOB.stickybanadminexemptions))
+ restore_stickybans()
+ for (var/oldban in (world.GetConfig("ban") - bannedkeys))
+ var/ckey = ckey(oldban)
+ if (ckey != oldban && (ckey in bannedkeys))
+ continue
+
+ var/list/ban = params2list(world.GetConfig("ban", oldban))
+ if (ban && !ban["fromdb"])
+ if (!import_raw_stickyban_to_db(ckey, ban))
+ log_world("Could not import stickyban on [oldban] into the database. Ignoring")
+ continue
+ dbcacheexpire = 0
+ bannedkeys += ckey
+ world.SetConfig("ban", oldban, null)
+
+ if (length(GLOB.stickybanadminexemptions)) //the previous loop can sleep
+ restore_stickybans()
+
for (var/bannedkey in bannedkeys)
var/ckey = ckey(bannedkey)
- var/list/ban = stickyban2list(world.GetConfig("ban", bannedkey))
+ var/list/ban = get_stickyban_from_ckey(bannedkey)
- //byond stores sticky bans by key, that can end up confusing things
- //i also remove it here so that if any stickybans cause a runtime, they just stop existing
- world.SetConfig("ban", bannedkey, null)
+ //byond stores sticky bans by key, that's lame
+ if (ckey != bannedkey)
+ world.SetConfig("ban", bannedkey, null)
if (!ban["ckey"])
ban["ckey"] = ckey
- //storing these can break things and isn't needed for sticky ban tracking
- ban -= "IP"
- ban -= "computer_id"
-
ban["matches_this_round"] = list()
ban["existing_user_matches_this_round"] = list()
ban["admin_matches_this_round"] = list()
+ ban["pending_matches_this_round"] = list()
+
cache[ckey] = ban
-
- for (var/bannedckey in cache)
- world.SetConfig("ban", bannedckey, list2stickyban(cache[bannedckey]))
+ world.SetConfig("ban", ckey, list2stickyban(ban))
return ..()
+
+/datum/controller/subsystem/stickyban/proc/Populatedbcache()
+ var/newdbcache = list() //so if we runtime or the db connection dies we don't kill the existing cache
+
+ var/datum/db_query/query_stickybans = SSdbcore.NewQuery("SELECT ckey, reason, banning_admin, datetime FROM [format_table_name("stickyban")] ORDER BY ckey")
+ var/datum/db_query/query_ckey_matches = SSdbcore.NewQuery("SELECT stickyban, matched_ckey, first_matched, last_matched, exempt FROM [format_table_name("stickyban_matched_ckey")] ORDER BY first_matched")
+ var/datum/db_query/query_cid_matches = SSdbcore.NewQuery("SELECT stickyban, matched_cid, first_matched, last_matched FROM [format_table_name("stickyban_matched_cid")] ORDER BY first_matched")
+ var/datum/db_query/query_ip_matches = SSdbcore.NewQuery("SELECT stickyban, INET_NTOA(matched_ip), first_matched, last_matched FROM [format_table_name("stickyban_matched_ip")] ORDER BY first_matched")
+
+ SSdbcore.QuerySelect(list(query_stickybans, query_ckey_matches, query_cid_matches, query_ip_matches))
+
+ if (query_stickybans.last_error)
+ qdel(query_stickybans)
+ qdel(query_ckey_matches)
+ qdel(query_cid_matches)
+ qdel(query_ip_matches)
+ return
+
+ while (query_stickybans.NextRow())
+ var/list/ban = list()
+
+ ban["ckey"] = query_stickybans.item[1]
+ ban["message"] = query_stickybans.item[2]
+ ban["reason"] = "(InGameBan)([query_stickybans.item[3]])"
+ ban["admin"] = query_stickybans.item[3]
+ ban["datetime"] = query_stickybans.item[4]
+ ban["type"] = list("sticky")
+
+ newdbcache["[query_stickybans.item[1]]"] = ban
+
+
+ if (!query_ckey_matches.last_error)
+ while (query_ckey_matches.NextRow())
+ var/list/match = list()
+
+ match["stickyban"] = query_ckey_matches.item[1]
+ match["matched_ckey"] = query_ckey_matches.item[2]
+ match["first_matched"] = query_ckey_matches.item[3]
+ match["last_matched"] = query_ckey_matches.item[4]
+ match["exempt"] = text2num(query_ckey_matches.item[5])
+
+ var/ban = newdbcache[query_ckey_matches.item[1]]
+ if (!ban)
+ continue
+ var/keys = ban[text2num(query_ckey_matches.item[5]) ? "whitelist" : "keys"]
+ if (!keys)
+ keys = ban[text2num(query_ckey_matches.item[5]) ? "whitelist" : "keys"] = list()
+ keys[query_ckey_matches.item[2]] = match
+
+ if (!query_cid_matches.last_error)
+ while (query_cid_matches.NextRow())
+ var/list/match = list()
+
+ match["stickyban"] = query_cid_matches.item[1]
+ match["matched_cid"] = query_cid_matches.item[2]
+ match["first_matched"] = query_cid_matches.item[3]
+ match["last_matched"] = query_cid_matches.item[4]
+
+ var/ban = newdbcache[query_cid_matches.item[1]]
+ if (!ban)
+ continue
+ var/computer_ids = ban["computer_id"]
+ if (!computer_ids)
+ computer_ids = ban["computer_id"] = list()
+ computer_ids[query_cid_matches.item[2]] = match
+
+
+ if (!query_ip_matches.last_error)
+ while (query_ip_matches.NextRow())
+ var/list/match = list()
+
+ match["stickyban"] = query_ip_matches.item[1]
+ match["matched_ip"] = query_ip_matches.item[2]
+ match["first_matched"] = query_ip_matches.item[3]
+ match["last_matched"] = query_ip_matches.item[4]
+
+ var/ban = newdbcache[query_ip_matches.item[1]]
+ if (!ban)
+ continue
+ var/IPs = ban["IP"]
+ if (!IPs)
+ IPs = ban["IP"] = list()
+ IPs[query_ip_matches.item[2]] = match
+
+ dbcache = newdbcache
+ dbcacheexpire = world.time+STICKYBAN_DB_CACHE_TIME
+
+ qdel(query_stickybans)
+ qdel(query_ckey_matches)
+ qdel(query_cid_matches)
+ qdel(query_ip_matches)
+
+
+/datum/controller/subsystem/stickyban/proc/import_raw_stickyban_to_db(ckey, list/ban)
+ . = FALSE
+ if (!ban["admin"])
+ ban["admin"] = "LEGACY"
+ if (!ban["message"])
+ ban["message"] = "Evasion"
+
+ var/datum/db_query/query_create_stickyban = SSdbcore.NewQuery(
+ "INSERT IGNORE INTO [format_table_name("stickyban")] (ckey, reason, banning_admin) VALUES (:ckey, :message, :admin)",
+ list("ckey" = ckey, "message" = ban["message"], "admin" = ban["admin"])
+ )
+ if (!query_create_stickyban.warn_execute())
+ qdel(query_create_stickyban)
+ return
+ qdel(query_create_stickyban)
+
+ var/list/sqlckeys = list()
+ var/list/sqlcids = list()
+ var/list/sqlips = list()
+
+ if (ban["keys"])
+ var/list/keys = splittext(ban["keys"], ",")
+ for (var/key in keys)
+ var/list/sqlckey = list()
+ sqlckey["stickyban"] = ckey
+ sqlckey["matched_ckey"] = ckey(key)
+ sqlckey["exempt"] = FALSE
+ sqlckeys[++sqlckeys.len] = sqlckey
+
+ if (ban["whitelist"])
+ var/list/keys = splittext(ban["whitelist"], ",")
+ for (var/key in keys)
+ var/list/sqlckey = list()
+ sqlckey["stickyban"] = ckey
+ sqlckey["matched_ckey"] = ckey(key)
+ sqlckey["exempt"] = TRUE
+ sqlckeys[++sqlckeys.len] = sqlckey
+
+ if (ban["computer_id"])
+ var/list/cids = splittext(ban["computer_id"], ",")
+ for (var/cid in cids)
+ var/list/sqlcid = list()
+ sqlcid["stickyban"] = ckey
+ sqlcid["matched_cid"] = cid
+ sqlcids[++sqlcids.len] = sqlcid
+
+ if (ban["IP"])
+ var/list/ips = splittext(ban["IP"], ",")
+ for (var/ip in ips)
+ var/list/sqlip = list()
+ sqlip["stickyban"] = ckey
+ sqlip["matched_ip"] = ip
+ sqlips[++sqlips.len] = sqlip
+
+ if (length(sqlckeys))
+ SSdbcore.MassInsert(format_table_name("stickyban_matched_ckey"), sqlckeys, ignore_errors = TRUE)
+
+ if (length(sqlcids))
+ SSdbcore.MassInsert(format_table_name("stickyban_matched_cid"), sqlcids, ignore_errors = TRUE)
+
+ if (length(sqlips))
+ SSdbcore.MassInsert(format_table_name("stickyban_matched_ip"), sqlips, ignore_errors = TRUE)
+
+
+ return TRUE
diff --git a/code/controllers/subsystem/sun.dm b/code/controllers/subsystem/sun.dm
index 442329cf46..746b1be7a9 100644
--- a/code/controllers/subsystem/sun.dm
+++ b/code/controllers/subsystem/sun.dm
@@ -1,32 +1,63 @@
+#define OCCLUSION_DISTANCE 20
+
+/datum/sun
+ var/azimuth = 0 // clockwise, top-down rotation from 0 (north) to 359
+ var/power_mod = 1 // how much power this sun is outputting relative to standard
+
+
+/datum/sun/vv_edit_var(var_name, var_value)
+ . = ..()
+ if(var_name == NAMEOF(src, azimuth))
+ SSsun.complete_movement()
+
+/atom/proc/check_obscured(datum/sun/sun, distance = OCCLUSION_DISTANCE)
+ var/target_x = round(sin(sun.azimuth), 0.01)
+ var/target_y = round(cos(sun.azimuth), 0.01)
+ var/x_hit = x
+ var/y_hit = y
+ var/turf/hit
+
+ for(var/run in 1 to distance)
+ x_hit += target_x
+ y_hit += target_y
+ hit = locate(round(x_hit, 1), round(y_hit, 1), z)
+ if(hit.opacity)
+ return TRUE
+ if(hit.x == 1 || hit.x == world.maxx || hit.y == 1 || hit.y == world.maxy) //edge of the map
+ break
+ return FALSE
+
SUBSYSTEM_DEF(sun)
name = "Sun"
wait = 1 MINUTES
flags = SS_NO_TICK_CHECK
- var/azimuth = 0 ///clockwise, top-down rotation from 0 (north) to 359
+ var/list/datum/sun/suns = list()
+ var/datum/sun/primary_sun
var/azimuth_mod = 1 ///multiplier against base_rotation
var/base_rotation = 6 ///base rotation in degrees per fire
/datum/controller/subsystem/sun/Initialize(start_timeofday)
- azimuth = rand(0, 359)
+ primary_sun = new
+ suns += primary_sun
+ primary_sun.azimuth = rand(0, 359)
azimuth_mod = round(rand(50, 200)/100, 0.01) // 50% - 200% of standard rotation
if(prob(50))
azimuth_mod *= -1
return ..()
/datum/controller/subsystem/sun/fire(resumed = FALSE)
- azimuth += azimuth_mod * base_rotation
- azimuth = round(azimuth, 0.01)
- if(azimuth >= 360)
- azimuth -= 360
- if(azimuth < 0)
- azimuth += 360
+ for(var/S in suns)
+ var/datum/sun/sun = S
+ sun.azimuth += azimuth_mod * base_rotation
+ sun.azimuth = round(sun.azimuth, 0.01)
+ if(sun.azimuth >= 360)
+ sun.azimuth -= 360
+ if(sun.azimuth < 0)
+ sun.azimuth += 360
complete_movement()
/datum/controller/subsystem/sun/proc/complete_movement()
- SEND_SIGNAL(src, COMSIG_SUN_MOVED, azimuth)
+ SEND_SIGNAL(src, COMSIG_SUN_MOVED, primary_sun, suns)
-/datum/controller/subsystem/sun/vv_edit_var(var_name, var_value)
- . = ..()
- if(var_name == NAMEOF(src, azimuth))
- complete_movement()
+#undef OCCLUSION_DISTANCE
diff --git a/code/controllers/subsystem/throwing.dm b/code/controllers/subsystem/throwing.dm
index 8c5abc5469..0fa8035d72 100644
--- a/code/controllers/subsystem/throwing.dm
+++ b/code/controllers/subsystem/throwing.dm
@@ -57,6 +57,7 @@ SUBSYSTEM_DEF(throwing)
var/dx
var/dy
var/force = MOVE_FORCE_DEFAULT
+ var/gentle = FALSE
var/pure_diagonal
var/diagonal_error
var/datum/callback/callback
@@ -64,15 +65,44 @@ SUBSYSTEM_DEF(throwing)
var/delayed_time = 0
var/last_move = 0
+
+/datum/thrownthing/New(thrownthing, target, target_turf, init_dir, maxrange, speed, thrower, diagonals_first, force, gentle, callback, target_zone)
+ . = ..()
+ src.thrownthing = thrownthing
+ RegisterSignal(thrownthing, COMSIG_PARENT_QDELETING, .proc/on_thrownthing_qdel)
+ src.target = target
+ src.target_turf = target_turf
+ src.init_dir = init_dir
+ src.maxrange = maxrange
+ src.speed = speed
+ src.thrower = thrower
+ src.diagonals_first = diagonals_first
+ src.force = force
+ src.gentle = gentle
+ src.callback = callback
+ src.target_zone = target_zone
+
+
/datum/thrownthing/Destroy()
+ if(HAS_TRAIT_FROM(thrownthing, TRAIT_SPOOKY_THROW, "revenant"))
+ REMOVE_TRAIT(thrownthing, TRAIT_SPOOKY_THROW, "revenant")
SSthrowing.processing -= thrownthing
thrownthing.throwing = null
thrownthing = null
target = null
thrower = null
- callback = 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)
@@ -112,7 +142,7 @@ SUBSYSTEM_DEF(throwing)
finalize()
return
- AM.Move(step, get_dir(AM, step))
+ AM.Move(step, get_dir(AM, step), DELAY_TO_GLIDE_SIZE(1 / speed))
if (!AM.throwing) // we hit something during our move
finalize(hit = TRUE)
@@ -136,15 +166,21 @@ SUBSYSTEM_DEF(throwing)
if (A == target)
hit = TRUE
thrownthing.throw_impact(A, 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()
diff --git a/code/controllers/subsystem/ticker.dm b/code/controllers/subsystem/ticker.dm
index 198c380f41..f37feeea34 100755
--- a/code/controllers/subsystem/ticker.dm
+++ b/code/controllers/subsystem/ticker.dm
@@ -69,6 +69,7 @@ SUBSYSTEM_DEF(ticker)
var/modevoted = FALSE //Have we sent a vote for the gamemode?
var/station_integrity = 100 // stored at roundend for use in some antag goals
+ var/emergency_reason
/datum/controller/subsystem/ticker/Initialize(timeofday)
load_mode()
@@ -268,7 +269,7 @@ SUBSYSTEM_DEF(ticker)
if(!GLOB.Debug2)
if(!can_continue)
log_game("[mode.name] failed pre_setup, cause: [mode.setup_error]")
- send2irc("SSticker", "[mode.name] failed pre_setup, cause: [mode.setup_error]")
+ send2adminchat("SSticker", "[mode.name] failed pre_setup, cause: [mode.setup_error]")
message_admins("[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.")
@@ -334,7 +335,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!"]")
+ send2adminchat("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)
@@ -563,7 +564,10 @@ SUBSYSTEM_DEF(ticker)
if(STATION_DESTROYED_NUKE)
news_message = "We would like to reassure all employees that the reports of a Syndicate backed nuclear attack on [station_name()] are, in fact, a hoax. Have a secure day!"
if(STATION_EVACUATED)
- news_message = "The crew of [station_name()] has been evacuated amid unconfirmed reports of enemy activity."
+ if(emergency_reason)
+ news_message = "[station_name()] has been evacuated after transmitting the following distress beacon:\n\n[emergency_reason]"
+ else
+ news_message = "The crew of [station_name()] has been evacuated amid unconfirmed reports of enemy activity."
if(BLOB_WIN)
news_message = "[station_name()] was overcome by an unknown biological outbreak, killing all crew on board. Don't let it happen to you! Remember, a clean work station is a safe work station."
if(BLOB_NUKE)
@@ -589,7 +593,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)
@@ -604,7 +608,8 @@ SUBSYSTEM_DEF(ticker)
if(SSblackbox.first_death)
var/list/ded = SSblackbox.first_death
if(ded.len)
- news_message += " NT Sanctioned Psykers picked up faint traces of someone near the station, allegedly having had died. Their name was: [ded["name"]], [ded["role"]], at [ded["area"]].[ded["last_words"] ? " Their last words were: \"[ded["last_words"]]\"" : ""]"
+ var/last_words = ded["last_words"] ? " Their last words were: \"[ded["last_words"]]\"" : ""
+ news_message += " NT Sanctioned Psykers picked up faint traces of someone near the station, allegedly having had died. Their name was: [ded["name"]], [ded["role"]], at [ded["area"]].[last_words]"
else
news_message += " NT Sanctioned Psykers proudly confirm reports that nobody died this shift!"
diff --git a/code/controllers/subsystem/time_track.dm b/code/controllers/subsystem/time_track.dm
index 2f4949fc1e..8a0a351d48 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 = 1 SECONDS
- flags = SS_NO_INIT|SS_NO_TICK_CHECK
+ wait = 10
+ flags = SS_NO_TICK_CHECK
+ init_order = INIT_ORDER_TIMETRACK
runlevels = RUNLEVEL_LOBBY | RUNLEVELS_DEFAULT
var/time_dilation_current = 0
@@ -16,33 +17,81 @@ SUBSYSTEM_DEF(time_track)
var/last_tick_byond_time = 0
var/last_tick_tickcount = 0
- var/last_measurement = 0
- var/measurement_delay = 60
-
- var/stat_time_text
- var/time_dilation_text
+/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"
+ 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"
+ )
+ )
/datum/controller/subsystem/time_track/fire()
- stat_time_text = "Server Time: [time2text(world.timeofday, "YYYY-MM-DD hh:mm:ss")]\n\nRound Time: [DisplayTimeText(world.time - SSticker.round_start_time, 1)] \n\nStation Time: [STATION_TIME_TIMESTAMP("hh:mm:ss", world.time)]\n\n[time_dilation_text]"
- if(++last_measurement == measurement_delay)
- last_measurement = 0
- var/current_realtime = REALTIMEOFDAY
- var/current_byondtime = world.time
- var/current_tickcount = world.time/world.tick_lag
+ var/current_realtime = REALTIMEOFDAY
+ var/current_byondtime = world.time
+ var/current_tickcount = world.time/world.tick_lag
+ GLOB.glide_size_multiplier = (current_byondtime - last_tick_byond_time) / (current_realtime - last_tick_realtime)
- if (!first_run)
- var/tick_drift = max(0, (((current_realtime - last_tick_realtime) - (current_byondtime - last_tick_byond_time)) / world.tick_lag))
+ if(times_fired % 10) // everything else is once every 10 seconds
+ return
- time_dilation_current = tick_drift / (current_tickcount - last_tick_tickcount) * 100
+ if (!first_run)
+ var/tick_drift = max(0, (((current_realtime - last_tick_realtime) - (current_byondtime - last_tick_byond_time)) / world.tick_lag))
- time_dilation_avg_fast = MC_AVERAGE_FAST(time_dilation_avg_fast, time_dilation_current)
- time_dilation_avg = MC_AVERAGE(time_dilation_avg, time_dilation_avg_fast)
- time_dilation_avg_slow = MC_AVERAGE_SLOW(time_dilation_avg_slow, time_dilation_avg)
- else
- first_run = FALSE
- last_tick_realtime = current_realtime
- last_tick_byond_time = current_byondtime
- last_tick_tickcount = current_tickcount
- 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]")))
- time_dilation_text = "Time Dilation: [round(time_dilation_current,1)]% AVG:([round(time_dilation_avg_fast,1)]%, [round(time_dilation_avg,1)]%, [round(time_dilation_avg_slow,1)]%)"
+ time_dilation_current = tick_drift / (current_tickcount - last_tick_tickcount) * 100
+
+ time_dilation_avg_fast = MC_AVERAGE_FAST(time_dilation_avg_fast, time_dilation_current)
+ time_dilation_avg = MC_AVERAGE(time_dilation_avg, time_dilation_avg_fast)
+ time_dilation_avg_slow = MC_AVERAGE_SLOW(time_dilation_avg_slow, time_dilation_avg)
+ else
+ first_run = FALSE
+ last_tick_realtime = current_realtime
+ last_tick_byond_time = current_byondtime
+ last_tick_tickcount = current_tickcount
+ 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,
+ SSair.get_active_turfs(), //does not return a list, which is what we want
+ SSair.get_amt_excited_groups(),
+ length(SSair.hotspots),
+ length(SSair.networks),
+ length(SSair.high_pressure_delta),
+ length(SSair.active_super_conductivity)
+ )
+ )
diff --git a/code/controllers/subsystem/timer.dm b/code/controllers/subsystem/timer.dm
index 398e23cdc1..48165be960 100644
--- a/code/controllers/subsystem/timer.dm
+++ b/code/controllers/subsystem/timer.dm
@@ -1,31 +1,51 @@
-#define BUCKET_LEN (world.fps*1*60) //how many ticks should we keep in the bucket. (1 minutes worth)
+/// 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 - SStimer.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 (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
+/// 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 just a series of circular doubly-linked
+ * lists. The object at a given index in bucket_list is a /datum/timedevent, the head of a circular list, which has prev
+ * and next references for the respective elements in that bucket's circular 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
-
+ priority = FIRE_PRIORITY_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?
+ /// Queue used for storing timers that do not fit into the current buckets
+ var/list/datum/timedevent/second_queue = list()
+ /// A hashlist dictionary used for storing unique timers
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 wrap around pivot for buckets. buckets before this are later running buckets wrapped around from the end of the bucket list.
+ 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
+ /// Contains the last time that a warning was issued for not invoking callbacks
var/static/last_invoke_warning = 0
+ /// Boolean operator controlling if the timer SS will automatically reset buckets if it fails to invoke callbacks for an extended period of time
var/static/bucket_auto_reset = TRUE
/datum/controller/subsystem/timer/PreInit()
@@ -38,44 +58,53 @@ SUBSYSTEM_DEF(timer)
return ..()
/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)
bucket_resolution = 0
- log_world("Timer bucket reset. world.time: [world.time], head_offset: [head_offset], practical_offset: [practical_offset]")
+ var/list/to_log = list("Timer bucket reset. world.time: [world.time], head_offset: [head_offset], practical_offset: [practical_offset]")
for (var/i in 1 to length(bucket_list))
var/datum/timedevent/bucket_head = bucket_list[i]
if (!bucket_head)
continue
- log_world("Active timers at index [i]:")
-
+ to_log += "Active timers at index [i]:"
var/datum/timedevent/bucket_node = bucket_head
var/anti_loop_check = 1000
do
- log_world(get_timer_debug_string(bucket_node))
+ to_log += get_timer_debug_string(bucket_node)
bucket_node = bucket_node.next
anti_loop_check--
while(bucket_node && bucket_node != bucket_head && anti_loop_check)
- log_world("Active timers in the second_queue queue:")
+
+ to_log += "Active timers in the second_queue queue:"
for(var/I in second_queue)
- log_world(get_timer_debug_string(I))
+ to_log += get_timer_debug_string(I)
- var/next_clienttime_timer_index = 0
- var/len = length(clienttime_timers)
+ // Dump all the logged data to the world log
+ log_world(to_log.Join("\n"))
- for (next_clienttime_timer_index in 1 to len)
+ // Process client-time timers
+ var/static/next_clienttime_timer_index = 0
+ 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
@@ -86,8 +115,8 @@ 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]")
+ 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
callBack.InvokeAsync()
@@ -95,135 +124,93 @@ 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, ctime_timer, timeToRun, COMPARE_KEY)
+ 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)
+ next_clienttime_timer_index = 0
if (MC_TICK_CHECK)
return
- var/static/list/spent = list()
- var/static/datum/timedevent/timer
+ // 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
- 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
-
- //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--
- 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
+ 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
- qtimer.timeToRun = world.time + qtimer.wait
- qtimer.bucketJoin()
+ qdel(timer)
- spent.len = 0
+ if (MC_TICK_CHECK)
+ break
-//formated this way to be runtime resistant
+ 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)
+ 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
+
+/**
+ * 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"]"
@@ -234,12 +221,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
@@ -247,25 +238,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))
@@ -273,34 +277,38 @@ SUBSYSTEM_DEF(timer)
if (!timer)
continue
- var/bucket_pos = BUCKET_POS(timer)
+ // Check that the TTR is within the range covered by buckets, when exceeded we've finished
if (timer.timeToRun >= TIMER_MAX)
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]")
+ 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
@@ -311,45 +319,64 @@ 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 as its representation in strings, useful for debugging
+ var/name
+ /// Next timed event in the bucket
var/datum/timedevent/next
+ /// Previous timed event in the bucket
var/datum/timedevent/prev
-/datum/timedevent/New(datum/callback/callBack, wait, flags, hash)
+/datum/timedevent/New(datum/callback/callBack, wait, flags, 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
- 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
+ // 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
- 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))
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)]")
@@ -390,23 +417,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)
+
+ // Store local references for the bucket list and secondary queue
+ // This is faster than referencing them from the datum itself
var/list/bucket_list = SStimer.bucket_list
var/list/second_queue = SStimer.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)
+ else if(timeToRun < TIMER_MAX)
SStimer.bucket_count--
else
var/l = length(second_queue)
second_queue -= src
if(l == length(second_queue))
SStimer.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
@@ -415,32 +458,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
-
if(L)
- BINARY_INSERT(src, L, datum/timedevent, src, timeToRun, COMPARE_KEY)
+ BINARY_INSERT(src, L, /datum/timedevent, src, timeToRun, COMPARE_KEY)
return
- //get the list of buckets
+ // Get a local reference to the bucket list, this is faster than referencing the datum
var/list/bucket_list = SStimer.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
+
+ // 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
@@ -448,7 +506,9 @@ SUBSYSTEM_DEF(timer)
next.prev = src
prev.next = src
-///Returns a string of the type of the callback for this timer
+/**
+ * Returns a string of the type of the callback for this timer
+ */
/datum/timedevent/proc/getcallingtype()
. = "ERROR"
if (callBack.object == GLOBAL_PROC)
@@ -457,14 +517,15 @@ SUBSYSTEM_DEF(timer)
. = "[callBack.object.type]"
/**
- * Create a new timer and insert it in the queue
- *
- * 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)
+ * 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, file, line)
if (!callback)
CRASH("addtimer called without a callback")
@@ -472,31 +533,30 @@ SUBSYSTEM_DEF(timer)
stack_trace("addtimer called with a negative wait. Converting to [world.tick_lag]")
if (callback.object != GLOBAL_PROC && QDELETED(callback.object) && !QDESTROYING(callback.object))
- stack_trace("addtimer called with a callback assigned to a qdeleted object. In the future such timers will not be supported and may refuse to run or run with a 0 wait")
+ stack_trace("addtimer called with a callback assigned to a qdeleted object. In the future such timers will not \
+ be supported and may refuse to run or run with a 0 wait")
wait = max(CEILING(wait, world.tick_lag), world.tick_lag)
if(wait >= INFINITY)
CRASH("Attempted to create timer with INFINITY delay")
+ // 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)
- hashlist = list(callback.object, "([REF(callback.object)])", callback.delegate, flags & TIMER_CLIENT_TIME)
- else
- hashlist = list(callback.object, "([REF(callback.object)])", callback.delegate, wait, flags & TIMER_CLIENT_TIME)
+ var/list/hashlist = list(callback.object, "([REF(callback.object)])", callback.delegate, flags & TIMER_CLIENT_TIME)
+ if(!(flags & TIMER_NO_HASH_WAIT))
+ hashlist += wait
hashlist += callback.arguments
hash = hashlist.Join("|||||||")
var/datum/timedevent/hash_timer = SStimer.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)
@@ -505,24 +565,23 @@ 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, hash, file && "[file]:[line]")
return timer.id
/**
- * Delete a timer
- *
- * Arguments:
- * * id a timerid or a /datum/timedevent
- */
+ * Delete a timer
+ *
+ * Arguments:
+ * * id a timerid or a /datum/timedevent
+ */
/proc/deltimer(id)
if (!id)
return FALSE
if (id == TIMER_ID_NULL)
CRASH("Tried to delete a null timerid. Use TIMER_STOPPABLE flag")
- if (!istext(id))
- if (istype(id, /datum/timedevent))
- qdel(id)
- return TRUE
+ if (istype(id, /datum/timedevent))
+ qdel(id)
+ return TRUE
//id is string
var/datum/timedevent/timer = SStimer.timer_id_dict[id]
if (timer && !timer.spent)
@@ -531,25 +590,22 @@ SUBSYSTEM_DEF(timer)
return FALSE
/**
- * Get the remaining deciseconds on a timer
- *
- * Arguments:
- * * id a timerid or a /datum/timedevent
- */
+ * Get the remaining deciseconds on a timer
+ *
+ * Arguments:
+ * * id a timerid or a /datum/timedevent
+ */
/proc/timeleft(id)
if (!id)
return null
if (id == TIMER_ID_NULL)
CRASH("Tried to get timeleft of a null timerid. Use TIMER_STOPPABLE flag")
- if (!istext(id))
- if (istype(id, /datum/timedevent))
- var/datum/timedevent/timer = id
- return timer.timeToRun - world.time
+ if (istype(id, /datum/timedevent))
+ var/datum/timedevent/timer = id
+ return timer.timeToRun - world.time
//id is string
var/datum/timedevent/timer = SStimer.timer_id_dict[id]
- if (timer && !timer.spent)
- return timer.timeToRun - world.time
- return null
+ return (timer && !timer.spent) ? timer.timeToRun - world.time : null
#undef BUCKET_LEN
#undef BUCKET_POS
diff --git a/code/controllers/subsystem/title.dm b/code/controllers/subsystem/title.dm
index bd843f959d..996f73ccf6 100644
--- a/code/controllers/subsystem/title.dm
+++ b/code/controllers/subsystem/title.dm
@@ -25,12 +25,12 @@ SUBSYSTEM_DEF(title)
SSmapping.HACK_LoadMapConfig()
for(var/S in provisional_title_screens)
var/list/L = splittext(S,"+")
- if((L.len == 1 && L[1] != "blank.png")|| (L.len > 1 && ((use_rare_screens && lowertext(L[1]) == "rare") || (lowertext(L[1]) == lowertext(SSmapping.config.map_name)))))
+ if((L.len == 1 && (L[1] != "exclude" && L[1] != "blank.png"))|| (L.len > 1 && ((use_rare_screens && lowertext(L[1]) == "rare") || (lowertext(L[1]) == lowertext(SSmapping.config.map_name)))))
title_screens += S
if(length(title_screens))
file_path = "[global.config.directory]/title_screens/images/[pick(title_screens)]"
-
+
if(!file_path)
file_path = "icons/default_title.dmi"
diff --git a/code/controllers/subsystem/vis_overlays.dm b/code/controllers/subsystem/vis_overlays.dm
index 0635709074..b0e5d6c689 100644
--- a/code/controllers/subsystem/vis_overlays.dm
+++ b/code/controllers/subsystem/vis_overlays.dm
@@ -5,10 +5,12 @@ SUBSYSTEM_DEF(vis_overlays)
init_order = INIT_ORDER_VIS
var/list/vis_overlay_cache
+ var/list/unique_vis_overlays
var/list/currentrun
/datum/controller/subsystem/vis_overlays/Initialize()
vis_overlay_cache = list()
+ unique_vis_overlays = list()
return ..()
/datum/controller/subsystem/vis_overlays/fire(resumed = FALSE)
@@ -29,31 +31,45 @@ SUBSYSTEM_DEF(vis_overlays)
return
//the "thing" var can be anything with vis_contents which includes images
-/datum/controller/subsystem/vis_overlays/proc/add_vis_overlay(atom/movable/thing, icon, iconstate, layer, plane, dir, alpha = 255, add_appearance_flags = NONE)
- . = "[icon]|[iconstate]|[layer]|[plane]|[dir]|[alpha]|[add_appearance_flags]"
- var/obj/effect/overlay/vis/overlay = vis_overlay_cache[.]
- if(!overlay)
- overlay = new
- overlay.icon = icon
- overlay.icon_state = iconstate
- overlay.layer = layer
- overlay.plane = plane
- overlay.dir = dir
- overlay.alpha = alpha
- overlay.appearance_flags |= add_appearance_flags
- vis_overlay_cache[.] = overlay
+/datum/controller/subsystem/vis_overlays/proc/add_vis_overlay(atom/movable/thing, icon, iconstate, layer, plane, dir, alpha = 255, add_appearance_flags = NONE, unique = FALSE)
+ var/obj/effect/overlay/vis/overlay
+ if(!unique)
+ . = "[icon]|[iconstate]|[layer]|[plane]|[dir]|[alpha]|[add_appearance_flags]"
+ overlay = vis_overlay_cache[.]
+ if(!overlay)
+ overlay = _create_new_vis_overlay(icon, iconstate, layer, plane, dir, alpha, add_appearance_flags)
+ vis_overlay_cache[.] = overlay
+ else
+ overlay.unused = 0
else
- overlay.unused = 0
+ overlay = _create_new_vis_overlay(icon, iconstate, layer, plane, dir, alpha, add_appearance_flags)
+ overlay.cache_expiration = -1
+ var/cache_id = "\ref[overlay]@{[world.time]}"
+ unique_vis_overlays += overlay
+ vis_overlay_cache[cache_id] = overlay
+ . = overlay
thing.vis_contents += overlay
if(!isatom(thing)) // Automatic rotation is not supported on non atoms
- return
+ return overlay
if(!thing.managed_vis_overlays)
thing.managed_vis_overlays = list(overlay)
- RegisterSignal(thing, COMSIG_ATOM_DIR_CHANGE, .proc/rotate_vis_overlay)
else
thing.managed_vis_overlays += overlay
+ return overlay
+
+/datum/controller/subsystem/vis_overlays/proc/_create_new_vis_overlay(icon, iconstate, layer, plane, dir, alpha, add_appearance_flags)
+ var/obj/effect/overlay/vis/overlay = new
+ overlay.icon = icon
+ overlay.icon_state = iconstate
+ overlay.layer = layer
+ overlay.plane = plane
+ overlay.dir = dir
+ overlay.alpha = alpha
+ overlay.appearance_flags |= add_appearance_flags
+ return overlay
+
/datum/controller/subsystem/vis_overlays/proc/remove_vis_overlay(atom/movable/thing, list/overlays)
thing.vis_contents -= overlays
@@ -62,15 +78,3 @@ SUBSYSTEM_DEF(vis_overlays)
thing.managed_vis_overlays -= overlays
if(!length(thing.managed_vis_overlays))
thing.managed_vis_overlays = null
- UnregisterSignal(thing, COMSIG_ATOM_DIR_CHANGE)
-
-/datum/controller/subsystem/vis_overlays/proc/rotate_vis_overlay(atom/thing, old_dir, new_dir)
- if(old_dir == new_dir)
- return
- var/rotation = dir2angle(old_dir) - dir2angle(new_dir)
- var/list/overlays_to_remove = list()
- for(var/i in thing.managed_vis_overlays)
- var/obj/effect/overlay/vis/overlay = i
- add_vis_overlay(thing, overlay.icon, overlay.icon_state, overlay.layer, overlay.plane, turn(overlay.dir, rotation), overlay.alpha, overlay.appearance_flags)
- overlays_to_remove += overlay
- remove_vis_overlay(thing, overlays_to_remove)
diff --git a/code/controllers/subsystem/vote.dm b/code/controllers/subsystem/vote.dm
index 880802fd5c..2474a6f272 100644
--- a/code/controllers/subsystem/vote.dm
+++ b/code/controllers/subsystem/vote.dm
@@ -68,6 +68,10 @@ SUBSYSTEM_DEF(vote)
//get the highest number of votes
var/greatest_votes = 0
var/total_votes = 0
+ if(mode == "gamemode" && CONFIG_GET(flag/must_be_readied_to_vote_gamemode))
+ for(var/mob/dead/new_player/P in GLOB.player_list)
+ if(P.ready != PLAYER_READY_TO_PLAY && voted[P.ckey])
+ choices[choices[voted[P.ckey]]]--
for(var/option in choices)
var/votes = choices[option]
total_votes += votes
@@ -101,6 +105,10 @@ SUBSYSTEM_DEF(vote)
/datum/controller/subsystem/vote/proc/calculate_condorcet_votes(var/blackbox_text)
// https://en.wikipedia.org/wiki/Schulze_method#Implementation
+ if((mode == "gamemode" || mode == "dynamic") && CONFIG_GET(flag/must_be_readied_to_vote_gamemode))
+ for(var/mob/dead/new_player/P in GLOB.player_list)
+ if(P.ready != PLAYER_READY_TO_PLAY && voted[P.ckey])
+ voted -= P.ckey
var/list/d[][] = new/list(choices.len,choices.len) // the basic vote matrix, how many times a beats b
for(var/ckey in voted)
var/list/this_vote = voted[ckey]
@@ -141,12 +149,16 @@ SUBSYSTEM_DEF(vote)
choices[choices[i]]++ // higher shortest path = better candidate, so we add to choices here
// choices[choices[i]] is the schulze ranking, here, rather than raw vote numbers
-/datum/controller/subsystem/vote/proc/calculate_majority_judgement_vote(var/blackbox_text)
- // https://en.wikipedia.org/wiki/Majority_judgment
+/datum/controller/subsystem/vote/proc/calculate_highest_median(var/blackbox_text)
+ // https://en.wikipedia.org/wiki/Highest_median_voting_rules
var/list/scores_by_choice = list()
for(var/choice in choices)
scores_by_choice += "[choice]"
scores_by_choice["[choice]"] = list()
+ if((mode == "gamemode" || mode == "dynamic") && CONFIG_GET(flag/must_be_readied_to_vote_gamemode))
+ for(var/mob/dead/new_player/P in GLOB.player_list)
+ if(P.ready != PLAYER_READY_TO_PLAY && voted[P.ckey])
+ voted -= P.ckey
for(var/ckey in voted)
var/list/this_vote = voted[ckey]
var/list/pretty_vote = list()
@@ -161,33 +173,24 @@ SUBSYSTEM_DEF(vote)
// END BALLOT GATHERING
for(var/score_name in scores_by_choice)
var/list/score = scores_by_choice[score_name]
- for(var/indiv_score in score)
- SSblackbox.record_feedback("nested tally","voting",1,list(blackbox_text,"Scores",score_name,GLOB.vote_score_options[indiv_score]))
- if(score.len == 0)
- scores_by_choice -= score_name
- while(scores_by_choice.len > 1)
- var/highest_median = 0
- for(var/score_name in scores_by_choice) // first get highest median
- var/list/score = scores_by_choice[score_name]
- if(!score.len)
- scores_by_choice -= score_name
- continue
+ if(!score.len)
+ choices[score_name] = 0
+ else
var/median = score[max(1,round(score.len/2))]
- if(median >= highest_median)
- highest_median = median
- for(var/score_name in scores_by_choice) // then, remove
- var/list/score = scores_by_choice[score_name]
- var/median = score[max(1,round(score.len/2))]
- if(median < highest_median)
- scores_by_choice -= score_name
- for(var/score_name in scores_by_choice) // after removals
- var/list/score = scores_by_choice[score_name]
- if(score.len == 0)
- choices[score_name] += 100 // we're in a tie situation--just go with the first one
- return
- var/median_pos = max(1,round(score.len/2))
- score.Cut(median_pos,median_pos+1)
- choices[score_name]++
+ var/p = 0 // proponents (those with higher than median)
+ var/q = 0 // opponents (lower than median)
+ var/list/this_score_list = scores_by_choice[score_name]
+ for(var/indiv_score in score)
+ SSblackbox.record_feedback("nested tally","voting",1,list(blackbox_text,"Scores",score_name,GLOB.vote_score_options[indiv_score]))
+ if(indiv_score < median) // this is possible to do in O(logn) but n is never more than 200 so this is fine
+ q += 1
+ else if(indiv_score > median)
+ p += 1
+ p /= this_score_list.len
+ q /= this_score_list.len
+ choices[score_name] = median + (((p - q) / (1 - p - q)) * 0.5) // usual judgement
+ // choices[score_name] = median + p - q // typical judgement
+ // choices[score_name] = median + (((p - q) / (p + q)) * 0.5) // central judgement
/datum/controller/subsystem/vote/proc/calculate_scores(var/blackbox_text)
for(var/choice in choices)
@@ -245,8 +248,8 @@ SUBSYSTEM_DEF(vote)
calculate_condorcet_votes(vote_title_text)
if(vote_system == SCORE_VOTING)
calculate_scores(vote_title_text)
- if(vote_system == MAJORITY_JUDGEMENT_VOTING)
- calculate_majority_judgement_vote(vote_title_text) // nothing uses this at the moment
+ if(vote_system == HIGHEST_MEDIAN_VOTING)
+ calculate_highest_median(vote_title_text) // nothing uses this at the moment
var/list/winners = vote_system == INSTANT_RUNOFF_VOTING ? get_runoff_results() : get_result()
var/was_roundtype_vote = mode == "roundtype" || mode == "dynamic"
if(winners.len > 0)
@@ -255,8 +258,8 @@ SUBSYSTEM_DEF(vote)
if(display_votes & SHOW_RESULTS)
if(vote_system == SCHULZE_VOTING)
text += "\nIt should be noted that this is not a raw tally of votes (impossible in ranked choice) but the score determined by the schulze method of voting, so the numbers will look weird!"
- if(vote_system == MAJORITY_JUDGEMENT_VOTING)
- text += "\nIt should be noted that this is not a raw tally of votes but the number of runoffs done by majority judgement!"
+ if(vote_system == HIGHEST_MEDIAN_VOTING)
+ text += "\nThis is the highest median score plus the tiebreaker!"
for(var/i=1,i<=choices.len,i++)
var/votes = choices[choices[i]]
if(!votes)
@@ -302,7 +305,7 @@ SUBSYSTEM_DEF(vote)
if(vote_system != SCORE_VOTING)
if(vote_system == SCHULZE_VOTING)
admintext += "\nIt should be noted that this is not a raw tally of votes (impossible in ranked choice) but the score determined by the schulze method of voting, so the numbers will look weird!"
- else if(vote_system == MAJORITY_JUDGEMENT_VOTING)
+ else if(vote_system == HIGHEST_MEDIAN_VOTING)
admintext += "\nIt should be noted that this is not a raw tally of votes but the number of runoffs done by majority judgement!"
for(var/i=1,i<=choices.len,i++)
var/votes = choices[choices[i]]
@@ -429,7 +432,7 @@ SUBSYSTEM_DEF(vote)
voted[usr.ckey] = list()
voted[usr.ckey] += vote
saved -= usr.ckey
- if(SCORE_VOTING,MAJORITY_JUDGEMENT_VOTING)
+ if(SCORE_VOTING,HIGHEST_MEDIAN_VOTING)
if(!(usr.ckey in voted))
voted += usr.ckey
voted[usr.ckey] = list()
@@ -584,7 +587,7 @@ SUBSYSTEM_DEF(vote)
. += "Vote any number of choices.
"
if(SCHULZE_VOTING,INSTANT_RUNOFF_VOTING)
. += "Vote by order of preference. Revoting will demote to the bottom. 1 is your favorite, and higher numbers are worse.
"
- if(SCORE_VOTING,MAJORITY_JUDGEMENT_VOTING)
+ if(SCORE_VOTING,HIGHEST_MEDIAN_VOTING)
. += "Grade the candidates by how much you like them.
"
. += "No-votes have no power--your opinion is only heard if you vote!
"
. += "Time Left: [DisplayTimeText(end_time-world.time)]
"
@@ -621,7 +624,7 @@ SUBSYSTEM_DEF(vote)
. += "(Saved!)"
. += "(Load vote from save)"
. += "(Reset votes)"
- if(SCORE_VOTING,MAJORITY_JUDGEMENT_VOTING)
+ if(SCORE_VOTING,HIGHEST_MEDIAN_VOTING)
var/list/myvote = voted[C.ckey]
for(var/i=1,i<=choices.len,i++)
. += "- [choices[i]]"
@@ -724,7 +727,7 @@ SUBSYSTEM_DEF(vote)
voted[usr.ckey] = SSpersistence.saved_votes[usr.ckey][mode]
if(islist(voted[usr.ckey]))
var/malformed = FALSE
- if(vote_system == SCORE_VOTING || vote_system == MAJORITY_JUDGEMENT_VOTING)
+ if(vote_system == SCORE_VOTING || vote_system == HIGHEST_MEDIAN_VOTING)
for(var/thing in voted[usr.ckey])
if(!(thing in choices))
malformed = TRUE
@@ -738,7 +741,7 @@ SUBSYSTEM_DEF(vote)
to_chat(usr,"Your saved vote was malformed! Start over!")
voted -= usr.ckey
else
- if(vote_system == SCORE_VOTING || vote_system == MAJORITY_JUDGEMENT_VOTING)
+ if(vote_system == SCORE_VOTING || vote_system == HIGHEST_MEDIAN_VOTING)
submit_vote(round(text2num(href_list["vote"])),round(text2num(href_list["score"])))
else
submit_vote(round(text2num(href_list["vote"])))
diff --git a/code/datums/achievements/_achievement_data.dm b/code/datums/achievements/_achievement_data.dm
new file mode 100644
index 0000000000..80f544a965
--- /dev/null
+++ b/code/datums/achievements/_achievement_data.dm
@@ -0,0 +1,149 @@
+///Datum that handles
+/datum/achievement_data
+ ///Ckey of this achievement data's owner
+ var/owner_ckey
+ ///Up to date list of all achievements and their info.
+ var/data = list()
+ ///Original status of achievement.
+ var/original_cached_data = list()
+ ///Have we done our set-up yet?
+ var/initialized = FALSE
+
+/datum/achievement_data/New(ckey)
+ owner_ckey = ckey
+ if(SSachievements.initialized && !initialized)
+ InitializeData()
+
+/datum/achievement_data/proc/InitializeData()
+ initialized = TRUE
+ load_all_achievements() //So we know which achievements we have unlocked so far.
+
+///Gets list of changed rows in MassInsert format
+/datum/achievement_data/proc/get_changed_data()
+ . = list()
+ for(var/T in data)
+ var/datum/award/A = SSachievements.awards[T]
+ if(data[T] != original_cached_data[T])//If our data from before is not the same as now, save it to db.
+ var/deets = A.get_changed_rows(owner_ckey,data[T])
+ if(deets)
+ . += list(deets)
+
+/datum/achievement_data/proc/load_all_achievements()
+ set waitfor = FALSE
+
+ var/list/kv = list()
+ var/datum/db_query/Query = SSdbcore.NewQuery(
+ "SELECT achievement_key,value FROM [format_table_name("achievements")] WHERE ckey = :ckey",
+ list("ckey" = owner_ckey)
+ )
+ if(!Query.Execute())
+ qdel(Query)
+ return
+ while(Query.NextRow())
+ var/key = Query.item[1]
+ var/value = text2num(Query.item[2])
+ kv[key] = value
+ qdel(Query)
+
+ for(var/T in subtypesof(/datum/award))
+ var/datum/award/A = SSachievements.awards[T]
+ if(!A || !A.name) //Skip abstract achievements types
+ continue
+ if(!data[T])
+ data[T] = A.parse_value(kv[A.database_id])
+ original_cached_data[T] = data[T]
+
+///Updates local cache with db data for the given achievement type if it wasn't loaded yet.
+/datum/achievement_data/proc/get_data(achievement_type)
+ var/datum/award/A = SSachievements.awards[achievement_type]
+ if(!A.name)
+ return FALSE
+ if(!data[achievement_type])
+ data[achievement_type] = A.load(owner_ckey)
+ original_cached_data[achievement_type] = data[achievement_type]
+
+///Unlocks an achievement of a specific type. achievement type is a typepath to the award, user is the mob getting the award, and value is an optional value to be used for defining a score to add to the leaderboard
+/datum/achievement_data/proc/unlock(achievement_type, mob/user, value = 1)
+ set waitfor = FALSE
+
+ if(!SSachievements.achievements_enabled)
+ return
+ var/datum/award/A = SSachievements.awards[achievement_type]
+ get_data(achievement_type) //Get the current status first if necessary
+ if(istype(A, /datum/award/achievement))
+ if(data[achievement_type]) //You already unlocked it so don't bother running the unlock proc
+ return
+ data[achievement_type] = TRUE
+ A.on_unlock(user) //Only on default achievement, as scores keep going up.
+ else if(istype(A, /datum/award/score))
+ data[achievement_type] += value
+
+///Getter for the status/score of an achievement
+/datum/achievement_data/proc/get_achievement_status(achievement_type)
+ return data[achievement_type]
+
+///Resets an achievement to default values.
+/datum/achievement_data/proc/reset(achievement_type)
+ if(!SSachievements.achievements_enabled)
+ return
+ var/datum/award/A = SSachievements.awards[achievement_type]
+ get_data(achievement_type)
+ if(istype(A, /datum/award/achievement))
+ data[achievement_type] = FALSE
+ else if(istype(A, /datum/award/score))
+ data[achievement_type] = 0
+
+/datum/achievement_data/ui_assets(mob/user)
+ return list(
+ get_asset_datum(/datum/asset/spritesheet/simple/achievements),
+ )
+
+/datum/achievement_data/ui_state(mob/user)
+ return GLOB.always_state
+
+/datum/achievement_data/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "Achievements")
+ ui.open()
+
+/datum/achievement_data/ui_data(mob/user)
+ var/ret_data = list() // screw standards (qustinnus you must rename src.data ok)
+ ret_data["categories"] = list("Bosses", "Misc", "Mafia", "Scores")
+ ret_data["achievements"] = list()
+ ret_data["user_key"] = user.ckey
+
+ var/datum/asset/spritesheet/simple/assets = get_asset_datum(/datum/asset/spritesheet/simple/achievements)
+ //This should be split into static data later
+ for(var/achievement_type in SSachievements.awards)
+ if(!SSachievements.awards[achievement_type].name) //No name? we a subtype.
+ continue
+ if(isnull(data[achievement_type])) //We're still loading
+ continue
+ var/list/this = list(
+ "name" = SSachievements.awards[achievement_type].name,
+ "desc" = SSachievements.awards[achievement_type].desc,
+ "category" = SSachievements.awards[achievement_type].category,
+ "icon_class" = assets.icon_class_name(SSachievements.awards[achievement_type].icon),
+ "value" = data[achievement_type],
+ "score" = ispath(achievement_type,/datum/award/score)
+ )
+ ret_data["achievements"] += list(this)
+
+ return ret_data
+
+/datum/achievement_data/ui_static_data(mob/user)
+ . = ..()
+ .["highscore"] = list()
+ for(var/score in SSachievements.scores)
+ var/datum/award/score/S = SSachievements.scores[score]
+ if(!S.name || !S.track_high_scores || !S.high_scores.len)
+ continue
+ .["highscore"] += list(list("name" = S.name,"scores" = S.high_scores))
+
+/client/verb/checkachievements()
+ set category = "OOC"
+ set name = "Check achievements"
+ set desc = "See all of your achievements!"
+
+ player_details.achievements.ui_interact(usr)
diff --git a/code/datums/achievements/_awards.dm b/code/datums/achievements/_awards.dm
new file mode 100644
index 0000000000..77485de8ba
--- /dev/null
+++ b/code/datums/achievements/_awards.dm
@@ -0,0 +1,117 @@
+/datum/award
+ ///Name of the achievement, If null it won't show up in the achievement browser. (Handy for inheritance trees)
+ var/name
+ var/desc = "You did it."
+ ///Found in UI_Icons/Achievements
+ var/icon = "default"
+ var/category = "Normal"
+
+ ///What ID do we use in db, limited to 32 characters
+ var/database_id
+ //Bump this up if you're changing outdated table identifier and/or achievement type
+ var/achievement_version = 2
+
+ //Value returned on db connection failure, in case we want to differ 0 and nonexistent later on
+ var/default_value = FALSE
+
+///This proc loads the achievement data from the hub.
+/datum/award/proc/load(key)
+ if(!SSdbcore.Connect())
+ return default_value
+ if(!key || !database_id || !name)
+ return default_value
+ var/raw_value = get_raw_value(key)
+ return parse_value(raw_value)
+
+///This saves the changed data to the hub.
+/datum/award/proc/get_changed_rows(key, value)
+ if(!database_id || !key || !name)
+ return
+ return list(
+ "ckey" = key,
+ "achievement_key" = database_id,
+ "value" = value,
+ )
+
+/datum/award/proc/get_metadata_row()
+ return list(
+ "achievement_key" = database_id,
+ "achievement_version" = achievement_version,
+ "achievement_type" = "award",
+ "achievement_name" = name,
+ "achievement_description" = desc,
+ )
+
+///Get raw numerical achievement value from the database
+/datum/award/proc/get_raw_value(key)
+ var/datum/db_query/Q = SSdbcore.NewQuery(
+ "SELECT value FROM [format_table_name("achievements")] WHERE ckey = :ckey AND achievement_key = :achievement_key",
+ list("ckey" = key, "achievement_key" = database_id)
+ )
+ if(!Q.Execute(async = TRUE))
+ qdel(Q)
+ return 0
+ var/result = 0
+ if(Q.NextRow())
+ result = text2num(Q.item[1])
+ qdel(Q)
+ return result
+
+//Should return sanitized value for achievement cache
+/datum/award/proc/parse_value(raw_value)
+ return default_value
+
+///Can be overriden for achievement specific events
+/datum/award/proc/on_unlock(mob/user)
+ return
+
+///Achievements are one-off awards for usually doing cool things.
+/datum/award/achievement
+ desc = "Achievement for epic people"
+
+/datum/award/achievement/get_metadata_row()
+ . = ..()
+ .["achievement_type"] = "achievement"
+
+/datum/award/achievement/parse_value(raw_value)
+ return raw_value > 0
+
+/datum/award/achievement/on_unlock(mob/user)
+ . = ..()
+ to_chat(user, "Achievement unlocked: [name]!")
+
+///Scores are for leaderboarded things, such as killcount of a specific boss
+/datum/award/score
+ desc = "you did it sooo many times."
+ category = "Scores"
+ default_value = 0
+
+ var/track_high_scores = TRUE
+ var/list/high_scores = list()
+
+/datum/award/score/New()
+ . = ..()
+ if(track_high_scores)
+ LoadHighScores()
+
+/datum/award/score/get_metadata_row()
+ . = ..()
+ .["achievement_type"] = "score"
+
+/datum/award/score/proc/LoadHighScores()
+ var/datum/db_query/Q = SSdbcore.NewQuery(
+ "SELECT ckey,value FROM [format_table_name("achievements")] WHERE achievement_key = :achievement_key ORDER BY value DESC LIMIT 50",
+ list("achievement_key" = database_id)
+ )
+ if(!Q.Execute(async = TRUE))
+ qdel(Q)
+ return
+ else
+ while(Q.NextRow())
+ var/key = Q.item[1]
+ var/score = text2num(Q.item[2])
+ high_scores[key] = score
+ qdel(Q)
+
+/datum/award/score/parse_value(raw_value)
+ return isnum(raw_value) ? raw_value : 0
diff --git a/code/datums/achievements/boss_achievements.dm b/code/datums/achievements/boss_achievements.dm
new file mode 100644
index 0000000000..104a369405
--- /dev/null
+++ b/code/datums/achievements/boss_achievements.dm
@@ -0,0 +1,130 @@
+/datum/award/achievement/boss
+ category = "Bosses"
+ icon = "baseboss"
+
+/datum/award/achievement/boss/tendril_exterminator
+ name = "Tendril Exterminator"
+ desc = "Watch your step"
+ database_id = BOSS_MEDAL_TENDRIL
+ icon = "tendril"
+
+/datum/award/achievement/boss/boss_killer
+ name = "Boss Killer"
+ desc = "You've come a long ways from asking how to switch hands."
+ database_id = "Boss Killer"
+ // icon = "firstboss"
+
+/datum/award/achievement/boss/blood_miner_kill
+ name = "Blood-Drunk Miner Killer"
+ desc = "I guess he couldn't handle his drink that well."
+ database_id = BOSS_MEDAL_MINER
+ icon = "miner"
+
+/datum/award/achievement/boss/demonic_miner_kill
+ name = "Demonic-Frost Miner Killer"
+ desc = "Definitely harder than the Blood-Drunk Miner."
+ database_id = BOSS_MEDAL_FROSTMINER
+
+/datum/award/achievement/boss/bubblegum_kill
+ name = "Bubblegum Killer"
+ desc = "I guess he wasn't made of candy after all"
+ database_id = BOSS_MEDAL_BUBBLEGUM
+ icon = "bbgum"
+
+/datum/award/achievement/boss/colossus_kill
+ name = "Colossus Killer"
+ desc = "The bigger they are... the better the loot"
+ database_id = BOSS_MEDAL_COLOSSUS
+ icon = "colossus"
+
+/datum/award/achievement/boss/drake_kill
+ name = "Drake Killer"
+ desc = "Now I can wear Rune Platebodies!"
+ database_id = BOSS_MEDAL_DRAKE
+ icon = "drake"
+
+/datum/award/achievement/boss/hierophant_kill
+ name = "Hierophant Killer"
+ desc = "Hierophant, but not triumphant."
+ database_id = BOSS_MEDAL_HIEROPHANT
+ icon = "hierophant"
+
+/datum/award/achievement/boss/legion_kill
+ name = "Legion Killer"
+ desc = "We were many..now we are none."
+ database_id = BOSS_MEDAL_LEGION
+ icon = "legion"
+
+/datum/award/achievement/boss/swarmer_beacon_kill
+ name = "Swarm Beacon Killer"
+ desc = "GET THEM OFF OF ME!"
+ database_id = BOSS_MEDAL_SWARMERS
+ icon = "swarmer"
+
+/datum/award/achievement/boss/wendigo_kill
+ name = "Wendigo Killer"
+ desc = "You've now ruined years of mythical storytelling."
+ database_id = BOSS_MEDAL_WENDIGO
+
+/datum/award/achievement/boss/blood_miner_crusher
+ name = "Blood-Drunk Miner Crusher"
+ desc = "I guess he couldn't handle his drink that well."
+ database_id = BOSS_MEDAL_MINER_CRUSHER
+ icon = "miner"
+
+/datum/award/achievement/boss/demonic_miner_crusher
+ name = "Demonic-Frost Miner Crusher"
+ desc = "Definitely harder than the Blood-Drunk Miner."
+ database_id = BOSS_MEDAL_FROSTMINER_CRUSHER
+
+/datum/award/achievement/boss/bubblegum_crusher
+ name = "Bubblegum Crusher"
+ desc = "I guess he wasn't made of candy after all"
+ database_id = BOSS_MEDAL_BUBBLEGUM_CRUSHER
+ icon = "bbgum"
+
+/datum/award/achievement/boss/colossus_crusher
+ name = "Colossus Crusher"
+ desc = "The bigger they are... the better the loot"
+ database_id = BOSS_MEDAL_COLOSSUS_CRUSHER
+ icon = "colossus"
+
+/datum/award/achievement/boss/drake_crusher
+ name = "Drake Crusher"
+ desc = "Now I can wear Rune Platebodies!"
+ database_id = BOSS_MEDAL_DRAKE_CRUSHER
+ icon = "drake"
+
+/datum/award/achievement/boss/hierophant_crusher
+ name = "Hierophant Crusher"
+ desc = "Hierophant, but not triumphant."
+ database_id = BOSS_MEDAL_HIEROPHANT_CRUSHER
+ icon = "hierophant"
+
+/datum/award/achievement/boss/legion_crusher
+ name = "Legion Crusher"
+ desc = "We were many... now we are none."
+ database_id = BOSS_MEDAL_LEGION_CRUSHER
+
+/datum/award/achievement/boss/swarmer_beacon_crusher
+ name = "Swarm Beacon Crusher"
+ desc = "GET THEM OFF OF ME!"
+ database_id = BOSS_MEDAL_SWARMERS_CRUSHER
+
+/datum/award/achievement/boss/wendigo_crusher
+ name = "Wendigo Crusher"
+ desc = "You've now ruined years of mythical storytelling."
+ database_id = BOSS_MEDAL_WENDIGO_CRUSHER
+
+//should be removed soon
+// /datum/award/achievement/boss/king_goat_kill
+// name = "King Goat Killer"
+// desc = "The king is dead, long live the king!"
+// database_id = BOSS_MEDAL_KINGGOAT
+// icon = "goatboss"
+
+// /datum/award/achievement/boss/king_goat_crusher
+// name = "King Goat Crusher"
+// desc = "The king is dead, long live the king!"
+// database_id = BOSS_MEDAL_KINGGOAT_CRUSHER
+// icon = "goatboss"
diff --git a/code/datums/achievements/boss_scores.dm b/code/datums/achievements/boss_scores.dm
new file mode 100644
index 0000000000..fdb9efa7c7
--- /dev/null
+++ b/code/datums/achievements/boss_scores.dm
@@ -0,0 +1,54 @@
+/datum/award/score/tendril_score
+ name = "Tendril Score"
+ desc = "Watch your step"
+ database_id = TENDRIL_CLEAR_SCORE
+
+/datum/award/score/boss_score
+ name = "Bosses Killed"
+ desc = "You've killed HOW many?"
+ database_id = BOSS_SCORE
+
+/datum/award/score/blood_miner_score
+ name = "Blood-Drunk Miners Killed"
+ desc = "You've killed HOW many?"
+ database_id = MINER_SCORE
+
+/datum/award/score/demonic_miner_score
+ name = "Demonic-Frost Miners Killed"
+ desc = "You've killed HOW many?"
+ database_id = FROST_MINER_SCORE
+
+/datum/award/score/bubblegum_score
+ name = "Bubblegums Killed"
+ desc = "You've killed HOW many?"
+ database_id = BUBBLEGUM_SCORE
+
+/datum/award/score/colussus_score
+ name = "Colossus Killed"
+ desc = "You've killed HOW many?"
+ database_id = COLOSSUS_SCORE
+
+/datum/award/score/drake_score
+ name = "Drakes Killed"
+ desc = "You've killed HOW many?"
+ database_id = DRAKE_SCORE
+
+/datum/award/score/hierophant_score
+ name = "Hierophants Killed"
+ desc = "You've killed HOW many?"
+ database_id = HIEROPHANT_SCORE
+
+/datum/award/score/legion_score
+ name = "Legions Killed"
+ desc = "You've killed HOW many?"
+ database_id = LEGION_SCORE
+
+/datum/award/score/swarmer_beacon_score
+ name = "Swarmer Beacons Killed"
+ desc = "You've killed HOW many?"
+ database_id = SWARMER_BEACON_SCORE
+
+/datum/award/score/wendigo_score
+ name = "Wendigos Killed"
+ desc = "You've killed HOW many?"
+ database_id = WENDIGO_SCORE
diff --git a/code/datums/achievements/mafia_achievements.dm b/code/datums/achievements/mafia_achievements.dm
new file mode 100644
index 0000000000..fbe3486397
--- /dev/null
+++ b/code/datums/achievements/mafia_achievements.dm
@@ -0,0 +1,115 @@
+/datum/award/achievement/mafia
+ category = "Mafia"
+ icon = "basemafia"
+
+///ALL THE ACHIEVEMENTS FOR WINNING A ROUND AS A ROLE///
+
+/datum/award/achievement/mafia/assistant
+ name = "Assistant Victory"
+ desc = "If you got killed instead of someone more important, you just flexed the true strength of your \"\"\"\"role\"\"\"\"."
+ database_id = MAFIA_MEDAL_ASSISTANT
+ icon = "town"
+
+/datum/award/achievement/mafia/detective
+ name = "Detective Victory"
+ desc = "If you did this with a Medical Doctor in the game, i'm not really that impressed."
+ database_id = MAFIA_MEDAL_DETECTIVE
+ icon = "town"
+
+/datum/award/achievement/mafia/psychologist
+ name = "Psychologist Victory"
+ desc = "You learned how to not reveal someone random night one! Or... maybe you're just a lucky bastard."
+ database_id = MAFIA_MEDAL_PSYCHOLOGIST
+ icon = "town"
+
+/datum/award/achievement/mafia/chaplain
+ name = "Chaplain Victory"
+ desc = "Useless... until the one night the thoughtfeeder confidently claims themselves as detective. Mafia's true bullshit detector."
+ database_id = MAFIA_MEDAL_CHAPLAIN
+ icon = "town"
+
+/datum/award/achievement/mafia/md
+ name = "Medical Doctor Victory"
+ desc = "Congratulations on learning how to not talk!"
+ database_id = MAFIA_MEDAL_MD
+ icon = "town"
+
+/datum/award/achievement/mafia/officer
+ name = "Security Officer Victory"
+ desc = "Don't worry, you can win this if you're dead! You... did use your ability to become dead, right?"
+ database_id = MAFIA_MEDAL_OFFICER
+ icon = "town"
+
+/datum/award/achievement/mafia/lawyer
+ name = "Lawyer Victory"
+ desc = "Oh don't mind me, i'm just the worst rol- Oops, I just instantly ended the game."
+ database_id = MAFIA_MEDAL_LAWYER
+ icon = "town"
+
+/datum/award/achievement/mafia/hop
+ name = "Head of Personnel Victory"
+ desc = "King of Assistants, waster of a single mafia's night, thrower of games."
+ database_id = MAFIA_MEDAL_HOP
+ icon = "town"
+
+/datum/award/achievement/mafia/warden
+ name = "Warden Victory"
+ desc = "Make changelings think you're detective, go on lockdown, actual detective investigates you and dies. Cha cha real smooth!"
+ database_id = MAFIA_MEDAL_WARDEN
+ icon = "town"
+
+/datum/award/achievement/mafia/hos
+ name = "Head of Security Victory"
+ desc = "Certified not shitcurity."
+ database_id = MAFIA_MEDAL_HOS
+ icon = "town"
+
+/datum/award/achievement/mafia/changeling
+ name = "Changeling Victory"
+ desc = "I think the changelings are metacomming."
+ database_id = MAFIA_MEDAL_CHANGELING
+ icon = "mafia"
+
+/datum/award/achievement/mafia/thoughtfeeder
+ name = "Thoughtfeeder Victory"
+ desc = "Clown's best friend. And Obsessed. And fugitive? Whose side are you on?!"
+ database_id = MAFIA_MEDAL_THOUGHTFEEDER
+ icon = "mafia"
+
+/datum/award/achievement/mafia/traitor
+ name = "Traitor Victory"
+ desc = "Guys, we still have two more changelings to ki-!! TRAITOR VICTORY !!"
+ database_id = MAFIA_MEDAL_TRAITOR
+ icon = "neutral"
+
+/datum/award/achievement/mafia/nightmare
+ name = "Nightmare Victory"
+ desc = "DID YOUR LIGHT FLICKER?!"
+ database_id = MAFIA_MEDAL_NIGHTMARE
+ icon = "neutral"
+
+/datum/award/achievement/mafia/fugitive
+ name = "Fugitive Victory"
+ desc = "I'm just the description on an achievement, but if you end up having to choose between town and changelings, go changelings."
+ database_id = MAFIA_MEDAL_FUGITIVE
+ icon = "neutral"
+
+/datum/award/achievement/mafia/obsessed
+ name = "Obsessed Victory"
+ desc = "You got your target lynched, so instead of being spiteful and annoying, you're just smug and annoying."
+ database_id = MAFIA_MEDAL_OBSESSED
+ icon = "neutral"
+
+/datum/award/achievement/mafia/clown
+ name = "Clown Victory"
+ desc = "Did you know this works on traitors, despite their immunity? If you hit the jackpot and manage to kill one, they'll salt into the next dimension. Clown tips!"
+ database_id = MAFIA_MEDAL_CLOWN
+ icon = "neutral"
+
+///ALL THE ACHIEVEMENTS FOR MISC MAFIA ODDITIES///
+
+/datum/award/achievement/mafia/universally_hated
+ name = "Universally Hated"
+ desc = "Managed to get more than 12 votes when put up on trial, jesus christ."
+ database_id = MAFIA_MEDAL_HATED
+ icon = "hated"
diff --git a/code/datums/achievements/misc_achievements.dm b/code/datums/achievements/misc_achievements.dm
new file mode 100644
index 0000000000..0da38df8f3
--- /dev/null
+++ b/code/datums/achievements/misc_achievements.dm
@@ -0,0 +1,161 @@
+/datum/award/achievement/misc
+ category = "Misc"
+ icon = "basemisc"
+
+/datum/award/achievement/misc/meteor_examine
+ name = "Your Life Before Your Eyes"
+ desc = "Take a close look at hurtling space debris"
+ database_id = MEDAL_METEOR
+ icon = "meteors"
+
+/datum/award/achievement/misc/pulse
+ name = "Jackpot"
+ desc = "Win a pulse rifle from an arcade machine"
+ database_id = MEDAL_PULSE
+ icon = "jackpot"
+
+/datum/award/achievement/misc/time_waste
+ name = "Time waster"
+ desc = "Speak no evil, hear no evil, see just errors"
+ database_id = MEDAL_TIMEWASTE
+ icon = "timewaste"
+
+/datum/award/achievement/misc/feat_of_strength
+ name = "Feat of Strength"
+ desc = "If the rod is immovable, is it passing you or are you passing it?"
+ database_id = MEDAL_RODSUPLEX
+ icon = "featofstrength"
+
+/datum/award/achievement/misc/round_and_full
+ name = "Round and Full"
+ desc = "Well at least you aren't down the river, I hear they eat people there."
+ database_id = MEDAL_CLOWNCARKING
+ icon = "clownking"
+
+/datum/award/achievement/misc/the_best_driver
+ name = "The Best Driver"
+ desc = "100 honks later"
+ database_id = MEDAL_THANKSALOT
+ icon = "clownthanks"
+
+/datum/award/achievement/misc/helbitaljanken
+ name = "Helbitaljanken"
+ desc = "You janked hard"
+ database_id = MEDAL_HELBITALJANKEN
+ icon = "helbital"
+
+/datum/award/achievement/misc/getting_an_upgrade
+ name = "Getting an upgrade"
+ desc = "Make your first unique material item!"
+ database_id = MEDAL_MATERIALCRAFT
+
+/datum/award/achievement/misc/rocket_holdup
+ name = "Disk, Please!"
+ desc = "Is the man currently pointing a loaded rocket launcher at your head point blank really dumb enough to pull the trigger? Do you really want to find out?"
+ database_id = MEDAL_DISKPLEASE
+
+/datum/award/achievement/misc/gamer
+ name = "My Watchlist Status is Not Important"
+ desc = "You may be under the impression that violent video games are a harmless pastime, but the security and medical personnel swarming your location with batons and knockout gas look like they disagree."
+ database_id = MEDAL_GAMER
+
+/datum/award/achievement/misc/vendor_squish
+ name = "I Was a Teenage Anarchist"
+ desc = "You were doing a great job sticking it to the system until that vending machine decided to fight back."
+ database_id = MEDAL_VENDORSQUISH
+
+/datum/award/achievement/misc/swirlie
+ name = "A Bowl-d New World"
+ desc = "There's a lot of grisly ways to kick it on the Spinward Periphery, but drowning to death in a toilet probably wasn't what you had in mind. Probably."
+ database_id = MEDAL_SWIRLIE
+
+/datum/award/achievement/misc/selfouch
+ name = "How Do I Switch Hands???"
+ desc = "If you saw someone casually club themselves upside the head with a toolbox anywhere in the galaxy but here, you'd probably be pretty concerned for them."
+ database_id = MEDAL_SELFOUCH
+
+/datum/award/achievement/misc/sandman
+ name = "Mister Sandman"
+ desc = "Mechanically speaking, there's no real benefit to being unconscious during surgery. Weird how insistent this doctor is about using the N2O anyway though, huh?"
+ database_id = MEDAL_SANDMAN
+
+/datum/award/achievement/misc/cleanboss
+ name = "One Lean, Mean, Cleaning Machine"
+ desc = "How does it feel to know that your workplace values a mop bucket on wheels more than you?" // i can do better than this give me time
+ database_id = MEDAL_CLEANBOSS
+
+/datum/award/achievement/misc/rule8
+ name = "Rule 8"
+ desc = "Call an admin this is ILLEGAL!!"
+ database_id = MEDAL_RULE8
+ icon = "rule8"
+
+/datum/award/achievement/misc/speed_round
+ name = "Long shift"
+ desc = "Well, that didn't take long."
+ database_id = MEDAL_LONGSHIFT
+ icon = "longshift"
+
+/datum/award/achievement/misc/snail
+ name = "KKKiiilll mmmeee"
+ desc = "You were a little too ambitious, but hey, I guess you're still alive?"
+ database_id = MEDAL_SNAIL
+ icon = "snail"
+
+/datum/award/achievement/misc/lookoutsir
+ name = "Look Out, Sir!"
+ desc = "Either awarded for making the ultimate sacrifice for your comrades, or a really dumb attempt at grenade jumping."
+ database_id = MEDAL_LOOKOUTSIR
+
+/datum/award/achievement/misc/gottem
+ name = "HA, GOTTEM"
+ desc = "Made you look!"
+ database_id = MEDAL_GOTTEM
+
+/datum/award/achievement/misc/ascension
+ name = "Ascension"
+ desc = "Caedite eos. Novit enim Dominus qui sunt eius."
+ database_id = MEDAL_ASCENSION
+ icon = "ascension"
+
+/datum/award/achievement/misc/frenching
+ name = "Frenching"
+ desc = "Just a taste, for science!"
+ database_id = MEDAL_FRENCHING
+ icon = "frenching"
+
+/datum/award/achievement/misc/ash_ascension
+ name = "Nightwatcher's Eyes"
+ desc = "You've risen above the flames, became one with the ashes. You've been reborn as one with the Nightwatcher."
+ database_id = MEDAL_ASH_ASCENSION
+ icon = "ashascend"
+
+/datum/award/achievement/misc/flesh_ascension
+ name = "Vortex of Arms"
+ desc = "You've became something more, something greater. A piece of the emperor resides within you, and you within him."
+ database_id = MEDAL_FLESH_ASCENSION
+ icon = "fleshascend"
+
+/datum/award/achievement/misc/rust_ascension
+ name = "Hills of Rust"
+ desc = "You've summoned a piece of the Hill of rust, and so the Hills welcome you."
+ database_id = MEDAL_RUST_ASCENSION
+ icon = "rustascend"
+
+/datum/award/achievement/misc/void_ascension
+ name = "All that perish"
+ desc = "Place of a different being, different time. Everything ends there... but maybe it is just the beginning?"
+ database_id = MEDAL_VOID_ASCENSION
+ icon = "voidascend"
+
+/datum/award/achievement/misc/toolbox_soul
+ name = "SOUL'd Out"
+ desc = "My eternal soul was destroyed to make a toolbox look funny and all I got was this achievement..."
+ database_id = MEDAL_TOOLBOX_SOUL
+ icon = "toolbox_soul"
+
+/datum/award/achievement/misc/chemistry_tut
+ name = "Perfect chemistry blossom"
+ desc = "Passed the chemistry tutorial with perfect purity!"
+ database_id = MEDAL_CHEM_TUT
+ icon = "chem_tut"
diff --git a/code/datums/achievements/misc_scores.dm b/code/datums/achievements/misc_scores.dm
new file mode 100644
index 0000000000..7ffc50c015
--- /dev/null
+++ b/code/datums/achievements/misc_scores.dm
@@ -0,0 +1,11 @@
+///How many times did we survive being a cripple?
+/datum/award/score/hardcore_random
+ name = "Hardcore random points"
+ desc = "Well, I might be a blind, deaf, crippled guy, but hey, at least I'm alive."
+ database_id = HARDCORE_RANDOM_SCORE
+
+///How many maintenance pills did you eat?
+/datum/award/score/maintenance_pill
+ name = "Maintenance Pills Consumed"
+ desc = "Wait why?"
+ database_id = MAINTENANCE_PILL_SCORE
diff --git a/code/datums/achievements/skill_achievements.dm b/code/datums/achievements/skill_achievements.dm
new file mode 100644
index 0000000000..7da936c61f
--- /dev/null
+++ b/code/datums/achievements/skill_achievements.dm
@@ -0,0 +1,10 @@
+/datum/award/achievement/skill
+ category = "Skills"
+ icon = "baseskill"
+
+/datum/award/achievement/skill/legendary_miner
+ name = "Legendary miner"
+ desc = "No mere rock can stop me!"
+ database_id = MEDAL_LEGENDARY_MINER
+ icon = "mining"
+
diff --git a/code/datums/brain_damage/imaginary_friend.dm b/code/datums/brain_damage/imaginary_friend.dm
index e5e1434ab8..f35389f171 100644
--- a/code/datums/brain_damage/imaginary_friend.dm
+++ b/code/datums/brain_damage/imaginary_friend.dm
@@ -92,7 +92,7 @@
trauma = _trauma
owner = trauma.owner
- setup_friend()
+ INVOKE_ASYNC(src, .proc/setup_friend)
join = new
join.Grant(src)
diff --git a/code/datums/callback.dm b/code/datums/callback.dm
index 62e10922f3..b5baea28f1 100644
--- a/code/datums/callback.dm
+++ b/code/datums/callback.dm
@@ -1,53 +1,53 @@
/**
- *# Callback Datums
- *A datum that holds a proc to be called on another object, used to track proccalls to other objects
- *
- * ## USAGE
- *
- * ```
- * var/datum/callback/C = new(object|null, /proc/type/path|"procstring", arg1, arg2, ... argn)
- * var/timerid = addtimer(C, time, timertype)
- * you can also use the compiler define shorthand
- * var/timerid = addtimer(CALLBACK(object|null, /proc/type/path|procstring, arg1, arg2, ... argn), time, timertype)
- * ```
- *
- * Note: proc strings can only be given for datum proc calls, global procs must be proc paths
- *
- * Also proc strings are strongly advised against because they don't compile error if the proc stops existing
- *
- * In some cases you can provide a shortform of the procname, see the proc typepath shortcuts documentation below
- *
- * ## INVOKING THE CALLBACK
- *`var/result = C.Invoke(args, to, add)` additional args are added after the ones given when the callback was created
- *
- * `var/result = C.InvokeAsync(args, to, add)` Asyncronous - returns . on the first sleep then continues on in the background
- * after the sleep/block ends, otherwise operates normally.
- *
- * ## PROC TYPEPATH SHORTCUTS
- * (these operate on paths, not types, so to these shortcuts, datum is NOT a parent of atom, etc...)
- *
- * ### global proc while in another global proc:
- * .procname
- *
- * `CALLBACK(GLOBAL_PROC, .some_proc_here)`
- *
- * ### proc defined on current(src) object (when in a /proc/ and not an override) OR overridden at src or any of it's parents:
- * .procname
- *
- * `CALLBACK(src, .some_proc_here)`
- *
- * ### when the above doesn't apply:
- *.proc/procname
- *
- * `CALLBACK(src, .proc/some_proc_here)`
- *
- *
- * proc defined on a parent of a some type
- *
- * `/some/type/.proc/some_proc_here`
- *
- * Otherwise you must always provide the full typepath of the proc (/type/of/thing/proc/procname)
- */
+ *# Callback Datums
+ *A datum that holds a proc to be called on another object, used to track proccalls to other objects
+ *
+ * ## USAGE
+ *
+ * ```
+ * var/datum/callback/C = new(object|null, /proc/type/path|"procstring", arg1, arg2, ... argn)
+ * var/timerid = addtimer(C, time, timertype)
+ * you can also use the compiler define shorthand
+ * var/timerid = addtimer(CALLBACK(object|null, /proc/type/path|procstring, arg1, arg2, ... argn), time, timertype)
+ * ```
+ *
+ * Note: proc strings can only be given for datum proc calls, global procs must be proc paths
+ *
+ * Also proc strings are strongly advised against because they don't compile error if the proc stops existing
+ *
+ * In some cases you can provide a shortform of the procname, see the proc typepath shortcuts documentation below
+ *
+ * ## INVOKING THE CALLBACK
+ *`var/result = C.Invoke(args, to, add)` additional args are added after the ones given when the callback was created
+ *
+ * `var/result = C.InvokeAsync(args, to, add)` Asyncronous - returns . on the first sleep then continues on in the background
+ * after the sleep/block ends, otherwise operates normally.
+ *
+ * ## PROC TYPEPATH SHORTCUTS
+ * (these operate on paths, not types, so to these shortcuts, datum is NOT a parent of atom, etc...)
+ *
+ * ### global proc while in another global proc:
+ * .procname
+ *
+ * `CALLBACK(GLOBAL_PROC, .some_proc_here)`
+ *
+ * ### proc defined on current(src) object (when in a /proc/ and not an override) OR overridden at src or any of it's parents:
+ * .procname
+ *
+ * `CALLBACK(src, .some_proc_here)`
+ *
+ * ### when the above doesn't apply:
+ *.proc/procname
+ *
+ * `CALLBACK(src, .proc/some_proc_here)`
+ *
+ *
+ * proc defined on a parent of a some type
+ *
+ * `/some/type/.proc/some_proc_here`
+ *
+ * Otherwise you must always provide the full typepath of the proc (/type/of/thing/proc/procname)
+ */
/datum/callback
///The object we will be calling the proc on
@@ -60,13 +60,13 @@
var/datum/weakref/user
/**
- * Create a new callback datum
- *
- * Arguments
- * * thingtocall the object to call the proc on
- * * proctocall the proc to call on the target object
- * * ... an optional list of extra arguments to pass to the proc
- */
+ * Create a new callback datum
+ *
+ * Arguments
+ * * thingtocall the object to call the proc on
+ * * proctocall the proc to call on the target object
+ * * ... an optional list of extra arguments to pass to the proc
+ */
/datum/callback/New(thingtocall, proctocall, ...)
if (thingtocall)
object = thingtocall
@@ -76,13 +76,13 @@
if(usr)
user = WEAKREF(usr)
/**
- * Immediately Invoke proctocall on thingtocall, with waitfor set to false
- *
- * Arguments:
- * * thingtocall Object to call on
- * * proctocall Proc to call on that object
- * * ... optional list of arguments to pass as arguments to the proc being called
- */
+ * Immediately Invoke proctocall on thingtocall, with waitfor set to false
+ *
+ * Arguments:
+ * * thingtocall Object to call on
+ * * proctocall Proc to call on that object
+ * * ... optional list of arguments to pass as arguments to the proc being called
+ */
/world/proc/ImmediateInvokeAsync(thingtocall, proctocall, ...)
set waitfor = FALSE
@@ -97,13 +97,13 @@
call(thingtocall, proctocall)(arglist(calling_arguments))
/**
- * Invoke this callback
- *
- * Calls the registered proc on the registered object, if the user ref
- * can be resolved it also inclues that as an arg
- *
- * If the datum being called on is varedited, the call is wrapped via WrapAdminProcCall
- */
+ * Invoke this callback
+ *
+ * Calls the registered proc on the registered object, if the user ref
+ * can be resolved it also inclues that as an arg
+ *
+ * If the datum being called on is varedited, the call is wrapped via [WrapAdminProcCall][/proc/WrapAdminProcCall]
+ */
/datum/callback/proc/Invoke(...)
if(!usr)
var/datum/weakref/W = user
@@ -130,13 +130,13 @@
return call(object, delegate)(arglist(calling_arguments))
/**
- * Invoke this callback async (waitfor=false)
- *
- * Calls the registered proc on the registered object, if the user ref
- * can be resolved it also inclues that as an arg
- *
- * If the datum being called on is varedited, the call is wrapped via WrapAdminProcCall
- */
+ * Invoke this callback async (waitfor=false)
+ *
+ * Calls the registered proc on the registered object, if the user ref
+ * can be resolved it also inclues that as an arg
+ *
+ * If the datum being called on is varedited, the call is wrapped via WrapAdminProcCall
+ */
/datum/callback/proc/InvokeAsync(...)
set waitfor = FALSE
@@ -166,7 +166,7 @@
/**
Helper datum for the select callbacks proc
- */
+ */
/datum/callback_select
var/list/finished
var/pendingcount
@@ -192,16 +192,16 @@
finished[index] = rtn
/**
- * Runs a list of callbacks asyncronously, returning only when all have finished
- *
- * Callbacks can be repeated, to call it multiple times
- *
- * Arguments:
- * * list/callbacks the list of callbacks to be called
- * * list/callback_args the list of lists of arguments to pass into each callback
- * * savereturns Optionally save and return the list of returned values from each of the callbacks
- * * resolution The number of byond ticks between each time you check if all callbacks are complete
- */
+ * Runs a list of callbacks asyncronously, returning only when all have finished
+ *
+ * Callbacks can be repeated, to call it multiple times
+ *
+ * Arguments:
+ * * list/callbacks the list of callbacks to be called
+ * * list/callback_args the list of lists of arguments to pass into each callback
+ * * savereturns Optionally save and return the list of returned values from each of the callbacks
+ * * resolution The number of byond ticks between each time you check if all callbacks are complete
+ */
/proc/callback_select(list/callbacks, list/callback_args, savereturns = TRUE, resolution = 1)
if (!callbacks)
return
diff --git a/code/datums/cinematic.dm b/code/datums/cinematic.dm
index 7b3081cb33..2648ae1eab 100644
--- a/code/datums/cinematic.dm
+++ b/code/datums/cinematic.dm
@@ -30,7 +30,7 @@
/datum/cinematic
var/id = CINEMATIC_DEFAULT
var/list/watching = list() //List of clients watching this
- var/list/locked = list() //Who had mob_transforming set during the cinematic
+ var/list/locked = list() //Who had mob_transforming set during the cinematic
var/is_global = FALSE //Global cinematics will override mob-specific ones
var/obj/screen/cinematic/screen
var/datum/callback/special_callback //For special effects synced with animation (explosions after the countdown etc)
@@ -45,7 +45,7 @@
if(!CC)
continue
var/client/C = CC
- //C.mob.clear_fullscreen("cinematic")
+ C.mob.clear_fullscreen("cinematic")
C.screen -= screen
watching = null
QDEL_NULL(screen)
@@ -54,7 +54,7 @@
if(!MM)
continue
var/mob/M = MM
- M.mob_transforming = FALSE
+ M.mob_transforming = FALSE
locked = null
return ..()
@@ -93,7 +93,7 @@
toggle_ooc(TRUE)
/datum/cinematic/proc/show_to(mob/M, client/C)
- //SIGNAL_HANDLER //must not wait.
+ SIGNAL_HANDLER
if(!M.mob_transforming)
locked += M
@@ -101,7 +101,7 @@
if(!C)
return
watching += C
- //M.overlay_fullscreen("cinematic",/obj/screen/fullscreen/cinematic_backdrop)
+ M.overlay_fullscreen("cinematic",/obj/screen/fullscreen/cinematic_backdrop)
C.screen += screen
//Sound helper
@@ -122,7 +122,7 @@
sleep(50)
/datum/cinematic/proc/replacement_cinematic(datum/source, datum/cinematic/other)
- //SIGNAL_HANDLER
+ SIGNAL_HANDLER
if(!is_global && other.is_global) //Allow it to play if we're local and it's global
return NONE
@@ -210,6 +210,20 @@
special()
screen.icon_state = "summary_cult"
+// /datum/cinematic/cult_fail
+// id = CINEMATIC_CULT_FAIL
+
+// /datum/cinematic/cult_fail/content()
+// screen.icon_state = "station_intact"
+// sleep(20)
+// cinematic_sound(sound('sound/creatures/narsie_rises.ogg'))
+// sleep(60)
+// cinematic_sound(sound('sound/effects/explosion_distant.ogg'))
+// sleep(10)
+// cinematic_sound(sound('sound/magic/demon_dies.ogg'))
+// sleep(30)
+// special()
+
/datum/cinematic/nuke_annihilation
id = CINEMATIC_ANNIHILATION
diff --git a/code/datums/components/combat_mode.dm b/code/datums/components/combat_mode.dm
index b9952e9133..29d90fd509 100644
--- a/code/datums/components/combat_mode.dm
+++ b/code/datums/components/combat_mode.dm
@@ -125,7 +125,7 @@
///Changes the user direction to (try) keep match the pointer.
/datum/component/combat_mode/proc/on_move(atom/movable/source, dir, atom/oldloc, forced)
var/mob/living/L = source
- if(mode_flags & COMBAT_MODE_ACTIVE && L.client && lastmousedir && lastmousedir != dir)
+ if((mode_flags & COMBAT_MODE_ACTIVE) && L.client)
L.setDir(lastmousedir, ismousemovement = TRUE)
/// Added movement delay if moving backward.
diff --git a/code/datums/components/crafting/recipes/recipes_misc.dm b/code/datums/components/crafting/recipes/recipes_misc.dm
index 1f4a47c474..d85df8c010 100644
--- a/code/datums/components/crafting/recipes/recipes_misc.dm
+++ b/code/datums/components/crafting/recipes/recipes_misc.dm
@@ -45,8 +45,8 @@
/datum/crafting_recipe/bloodsucker/blackcoffin
name = "Black Coffin"
result = /obj/structure/closet/crate/coffin/blackcoffin
- tools = list(/obj/item/weldingtool,
- /obj/item/screwdriver)
+ tools = list(TOOL_WELDER,
+ TOOL_SCREWDRIVER)
reqs = list(/obj/item/stack/sheet/cloth = 1,
/obj/item/stack/sheet/mineral/wood = 5,
/obj/item/stack/sheet/metal = 1)
@@ -72,8 +72,8 @@
/datum/crafting_recipe/bloodsucker/metalcoffin
name = "Metal Coffin"
result =/obj/structure/closet/crate/coffin/metalcoffin
- tools = list(/obj/item/weldingtool,
- /obj/item/screwdriver)
+ tools = list(TOOL_WELDER,
+ TOOL_SCREWDRIVER)
reqs = list(/obj/item/stack/sheet/metal = 5)
time = 100
subcategory = CAT_FURNITURE
@@ -84,9 +84,9 @@
name = "Persuasion Rack"
//desc = "For converting crewmembers into loyal Vassals."
result = /obj/structure/bloodsucker/vassalrack
- tools = list(/obj/item/weldingtool,
- //obj/item/screwdriver,
- /obj/item/wrench
+ tools = list(TOOL_WELDER,
+ //TOOL_SCREWDRIVER,
+ TOOL_WRENCH
)
reqs = list(/obj/item/stack/sheet/mineral/wood = 3,
/obj/item/stack/sheet/metal = 2,
@@ -108,8 +108,8 @@
name = "Candelabrum"
//desc = "For converting crewmembers into loyal Vassals."
result = /obj/structure/bloodsucker/candelabrum
- tools = list(/obj/item/weldingtool,
- /obj/item/wrench
+ tools = list(TOOL_WELDER,
+ TOOL_WRENCH
)
reqs = list(/obj/item/stack/sheet/metal = 3,
/obj/item/stack/rods = 1,
diff --git a/code/datums/components/footstep.dm b/code/datums/components/footstep.dm
index f767c607a5..8b326ac424 100644
--- a/code/datums/components/footstep.dm
+++ b/code/datums/components/footstep.dm
@@ -46,7 +46,7 @@
var/mob/living/LM = parent
if(!T.footstep || LM.buckled || !CHECK_MOBILITY(LM, MOBILITY_STAND) || LM.buckled || LM.throwing || (LM.movement_type & (VENTCRAWLING | FLYING)))
if (LM.lying && !LM.buckled && !(!T.footstep || LM.movement_type & (VENTCRAWLING | FLYING))) //play crawling sound if we're lying
- playsound(T, 'sound/effects/footstep/crawl1.ogg', 15 * volume)
+ playsound(T, 'sound/effects/footstep/crawl1.ogg', 15 * volume, falloff_distance = 1)
return
if(HAS_TRAIT(LM, TRAIT_SILENT_STEP))
@@ -75,7 +75,7 @@
if(!T)
return
if(isfile(footstep_sounds) || istext(footstep_sounds))
- playsound(T, footstep_sounds, volume)
+ playsound(T, footstep_sounds, volume, falloff_distance = 1)
return
var/turf_footstep
switch(footstep_type)
@@ -89,7 +89,7 @@
turf_footstep = T.footstep
if(!turf_footstep)
return
- playsound(T, pick(footstep_sounds[turf_footstep][1]), footstep_sounds[turf_footstep][2] * volume, TRUE, footstep_sounds[turf_footstep][3] + e_range)
+ playsound(T, pick(footstep_sounds[turf_footstep][1]), footstep_sounds[turf_footstep][2] * volume, TRUE, footstep_sounds[turf_footstep][3] + e_range, falloff_distance = 1)
/datum/component/footstep/proc/play_humanstep()
var/turf/open/T = prepare_step()
@@ -114,10 +114,10 @@
turf_footstep = T.footstep
L = GLOB.footstep
if(FOOTSTEP_MOB_SLIME)
- playsound(T, 'sound/effects/footstep/slime1.ogg', 50 * volume)
+ playsound(T, 'sound/effects/footstep/slime1.ogg', 50 * volume, falloff_distance = 1)
return
if(FOOTSTEP_MOB_CRAWL)
- playsound(T, 'sound/effects/footstep/crawl1.ogg', 50 * volume)
+ playsound(T, 'sound/effects/footstep/crawl1.ogg', 50 * volume, falloff_distance = 1)
return
special = TRUE
else
@@ -126,13 +126,13 @@
playsound(T, pick(GLOB.footstep[T.footstep][1]),
GLOB.footstep[T.footstep][2] * volume,
TRUE,
- GLOB.footstep[T.footstep][3] + e_range)
+ GLOB.footstep[T.footstep][3] + e_range, falloff_distance = 1)
return
if(!special && H.dna.species.special_step_sounds)
- playsound(T, pick(H.dna.species.special_step_sounds), 50, TRUE)
+ playsound(T, pick(H.dna.species.special_step_sounds), 50, TRUE, falloff_distance = 1)
else
playsound(T, pick(L[turf_footstep][1]),
L[turf_footstep][2] * volume,
TRUE,
- L[turf_footstep][3] + e_range)
+ L[turf_footstep][3] + e_range, falloff_distance = 1)
diff --git a/code/datums/components/gps.dm b/code/datums/components/gps.dm
index c2b3ad1f30..f3ff912321 100644
--- a/code/datums/components/gps.dm
+++ b/code/datums/components/gps.dm
@@ -21,12 +21,15 @@ GLOBAL_LIST_EMPTY(GPS_list)
var/updating = TRUE //Automatic updating of GPS list. Can be set to manual by user.
var/global_mode = TRUE //If disabled, only GPS signals of the same Z level are shown
-/datum/component/gps/item/Initialize(_gpstag = "COM0", emp_proof = FALSE)
+/datum/component/gps/item/Initialize(_gpstag = "COM0", emp_proof = FALSE, starton = TRUE)
. = ..()
if(. == COMPONENT_INCOMPATIBLE || !isitem(parent))
return COMPONENT_INCOMPATIBLE
var/atom/A = parent
- A.add_overlay("working")
+ if(starton)
+ A.add_overlay("working")
+ else
+ tracking = FALSE
A.name = "[initial(A.name)] ([gpstag])"
RegisterSignal(parent, COMSIG_ITEM_ATTACK_SELF, .proc/interact)
if(!emp_proof)
diff --git a/code/datums/components/material_container.dm b/code/datums/components/material_container.dm
index 7adc634621..cc988544b8 100644
--- a/code/datums/components/material_container.dm
+++ b/code/datums/components/material_container.dm
@@ -10,25 +10,37 @@
*/
/datum/component/material_container
+ /// The total amount of materials this material container contains
var/total_amount = 0
+ /// The maximum amount of materials this material container can contain
var/max_amount
- var/sheet_type
+ /// Map of material ref -> amount
var/list/materials //Map of key = material ref | Value = amount
+ /// The list of materials that this material container can accept
+ var/list/allowed_materials
var/show_on_examine
var/disable_attackby
var/list/allowed_typecache
+ /// The last main material that was inserted into this container
var/last_inserted_id
+ /// Whether or not this material container allows specific amounts from sheets to be inserted
var/precise_insertion = FALSE
+ /// A callback invoked before materials are inserted into this container
var/datum/callback/precondition
+ /// A callback invoked after materials are inserted into this container
var/datum/callback/after_insert
/// Sets up the proper signals and fills the list of materials with the appropriate references.
/datum/component/material_container/Initialize(list/mat_list, max_amt = 0, _show_on_examine = FALSE, list/allowed_types, datum/callback/_precondition, datum/callback/_after_insert, _disable_attackby)
+ if(!isatom(parent))
+ return COMPONENT_INCOMPATIBLE
+
materials = list()
max_amount = max(0, max_amt)
show_on_examine = _show_on_examine
disable_attackby = _disable_attackby
+ allowed_materials = mat_list || list()
if(allowed_types)
if(ispath(allowed_types) && allowed_types == /obj/item/stack)
allowed_typecache = GLOB.typecache_stack
@@ -38,14 +50,32 @@
precondition = _precondition
after_insert = _after_insert
- RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, .proc/OnAttackBy)
- RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/OnExamine)
+ RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, .proc/on_attackby)
+ RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/on_examine)
- for(var/mat in mat_list) //Make the assoc list ref | amount
- var/datum/material/M = SSmaterials.GetMaterialRef(mat)
- materials[M] = 0
+ for(var/mat in mat_list) //Make the assoc list material reference -> amount
+ var/mat_ref = SSmaterials.GetMaterialRef(mat)
+ if(isnull(mat_ref))
+ continue
+ var/mat_amt = mat_list[mat]
+ if(isnull(mat_amt))
+ mat_amt = 0
+ materials[mat_ref] += mat_amt
+
+/datum/component/material_container/Destroy(force, silent)
+ materials = null
+ allowed_typecache = null
+ // if(insertion_check)
+ // QDEL_NULL(insertion_check)
+ if(precondition)
+ QDEL_NULL(precondition)
+ if(after_insert)
+ QDEL_NULL(after_insert)
+ return ..()
+
+/datum/component/material_container/proc/on_examine(datum/source, mob/user, list/examine_list)
+ SIGNAL_HANDLER
-/datum/component/material_container/proc/OnExamine(datum/source, mob/user, list/examine_list)
if(show_on_examine)
for(var/I in materials)
var/datum/material/M = I
@@ -54,7 +84,9 @@
examine_list += "It has [amt] units of [lowertext(M.name)] stored."
/// Proc that allows players to fill the parent with mats
-/datum/component/material_container/proc/OnAttackBy(datum/source, obj/item/I, mob/living/user)
+/datum/component/material_container/proc/on_attackby(datum/source, obj/item/I, mob/living/user)
+ SIGNAL_HANDLER
+
var/list/tc = allowed_typecache
if(disable_attackby)
return
@@ -63,48 +95,104 @@
if(I.item_flags & ABSTRACT)
return
if((I.flags_1 & HOLOGRAM_1) || (I.item_flags & NO_MAT_REDEMPTION) || (tc && !is_type_in_typecache(I, tc)))
+ // if(!(mat_container_flags & MATCONTAINER_SILENT))
to_chat(user, "[parent] won't accept [I]!")
return
. = COMPONENT_NO_AFTERATTACK
var/datum/callback/pc = precondition
if(pc && !pc.Invoke(user))
return
- var/material_amount = get_item_material_amount(I)
+ var/material_amount = get_item_material_amount(I) //, mat_container_flags)
if(!material_amount)
to_chat(user, "[I] does not contain sufficient materials to be accepted by [parent].")
return
if((!precise_insertion || !GLOB.typecache_stack[I.type]) && !has_space(material_amount))
- to_chat(user, "[parent] has not enough space. Please remove materials from [parent] in order to insert more.")
+ to_chat(user, "[parent] is full. Please remove materials from [parent] in order to insert more.")
return
- user_insert(I, user)
+ user_insert(I, user) //, mat_container_flags)
/// Proc used for when player inserts materials
-/datum/component/material_container/proc/user_insert(obj/item/I, mob/living/user)
+/datum/component/material_container/proc/user_insert(obj/item/I, mob/living/user, datum/component/remote_materials/remote = null)
set waitfor = FALSE
- var/requested_amount
var/active_held = user.get_active_held_item() // differs from I when using TK
- if(istype(I, /obj/item/stack) && precise_insertion)
- var/atom/current_parent = parent
+ var/inserted = 0
+
+ //handle stacks specially
+ if(istype(I, /obj/item/stack))
+ var/atom/current_parent = remote ? remote.parent : parent //is the user using a remote materials component?
var/obj/item/stack/S = I
- requested_amount = input(user, "How much do you want to insert?", "Inserting [S.singular_name]s") as num|null
+
+ //try to get ammount to use
+ var/requested_amount
+ if(precise_insertion)
+ requested_amount = input(user, "How much do you want to insert?", "Inserting [S.singular_name]s") as num|null
+ else
+ requested_amount= S.amount
+
if(isnull(requested_amount) || (requested_amount <= 0))
return
- if(QDELETED(I) || QDELETED(user) || QDELETED(src) || parent != current_parent || user.physical_can_use_topic(current_parent) < UI_INTERACTIVE || user.get_active_held_item() != active_held)
+ if(QDELETED(I) || QDELETED(user) || QDELETED(src) || user.get_active_held_item() != active_held)
return
- if(!user.temporarilyRemoveItemFromInventory(I))
- to_chat(user, "[I] is stuck to you and cannot be placed into [parent].")
- return
- var/inserted = insert_item(I, stack_amt = requested_amount)
+ //are we still in range after the user input?
+ if((remote ? remote.parent : parent) != current_parent || user.physical_can_use_topic(current_parent) < UI_INTERACTIVE)
+ return
+ inserted = insert_stack(S, requested_amount)
+ else
+ if(!user.temporarilyRemoveItemFromInventory(I))
+ to_chat(user, "[I] is stuck to you and cannot be placed into [parent].")
+ return
+ inserted = insert_item(I)
+ qdel(I)
+
if(inserted)
to_chat(user, "You insert a material total of [inserted] into [parent].")
- qdel(I)
if(after_insert)
after_insert.Invoke(I, last_inserted_id, inserted)
- else if(I == active_held)
- user.put_in_active_hand(I)
+ if(remote && remote.after_insert)
+ remote.after_insert.Invoke(I, last_inserted_id, inserted)
+
+//Inserts a number of sheets from a stack, returns the amount of sheets used.
+/datum/component/material_container/proc/insert_stack(obj/item/stack/S, amt, multiplier = 1)
+ if(isnull(amt))
+ amt = S.amount
+
+ if(amt <= 0)
+ return FALSE
+
+ if(amt > S.amount)
+ amt = S.amount
+
+ var/material_amt = get_item_material_amount(S)
+ if(!material_amt)
+ return FALSE
+
+ //get max number of sheets we have room to add
+ var/mat_per_sheet = material_amt/S.amount
+ amt = min(amt, round((max_amount - total_amount) / (mat_per_sheet)))
+ if(!amt)
+ return FALSE
+
+ //add the mats and keep track of how much was added
+ var/starting_total = total_amount
+ for(var/MAT in materials)
+ materials[MAT] += S.mats_per_unit[MAT] * amt * multiplier
+ total_amount += S.mats_per_unit[MAT] * amt * multiplier
+ var/total_added = total_amount - starting_total
+
+ //update last_inserted_id with mat making up majority of the stack
+ var/primary_mat
+ var/max_mat_value = 0
+ for(var/MAT in materials)
+ if(S.mats_per_unit[MAT] > max_mat_value)
+ max_mat_value = S.mats_per_unit[MAT]
+ primary_mat = MAT
+ last_inserted_id = primary_mat
+
+ S.use(amt)
+ return total_added
/// 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)
+/datum/component/material_container/proc/insert_item(obj/item/I, var/multiplier = 1)
if(QDELETED(I))
return FALSE
@@ -117,16 +205,46 @@
last_inserted_id = insert_item_materials(I, multiplier)
return material_amount
+/**
+ * Inserts the relevant materials from an item into this material container.
+ *
+ * Arguments:
+ * - [source][/obj/item]: The source of the materials we are inserting.
+ * - multiplier: The multiplier for the materials being inserted.
+ * - breakdown_flags: The breakdown bitflags that will be used to retrieve the materials from the source
+ */
/datum/component/material_container/proc/insert_item_materials(obj/item/I, multiplier = 1)
var/primary_mat
var/max_mat_value = 0
- for(var/MAT in materials)
- materials[MAT] += I.custom_materials[MAT] * multiplier
- total_amount += I.custom_materials[MAT] * multiplier
- if(I.custom_materials[MAT] > max_mat_value)
+ var/list/item_materials = I.custom_materials
+ for(var/MAT in item_materials)
+ if(!can_hold_material(MAT))
+ continue
+ materials[MAT] += item_materials[MAT] * multiplier
+ total_amount += item_materials[MAT] * multiplier
+ if(item_materials[MAT] > max_mat_value)
+ max_mat_value = item_materials[MAT]
primary_mat = MAT
+
return primary_mat
+/**
+ * The default check for whether we can add materials to this material container.
+ *
+ * Arguments:
+ * - [mat][/atom/material]: The material we are checking for insertability.
+ */
+/datum/component/material_container/proc/can_hold_material(datum/material/mat)
+ if(mat in allowed_typecache)
+ return TRUE
+ if(istype(mat) && ((mat.id in allowed_typecache) || (mat.type in allowed_materials)))
+ allowed_materials += mat // This could get messy with passing lists by ref... but if you're doing that the list expansion is probably being taken care of elsewhere anyway...
+ return TRUE
+ // if(insertion_check?.Invoke(mat))
+ // allowed_materials += mat
+ // return TRUE
+ return FALSE
+
/// For inserting an amount of material
/datum/component/material_container/proc/insert_amount_mat(amt, var/datum/material/mat)
if(!istype(mat))
@@ -135,6 +253,7 @@
var/total_amount_saved = total_amount
if(mat)
materials[mat] += amt
+ total_amount += amt
else
for(var/i in materials)
materials[i] += amt
diff --git a/code/datums/components/orbiter.dm b/code/datums/components/orbiter.dm
index 49b19f767c..26f52f6ba5 100644
--- a/code/datums/components/orbiter.dm
+++ b/code/datums/components/orbiter.dm
@@ -146,9 +146,11 @@
if(!istype(A) || !get_turf(A) || A == src)
return
+ orbit_target = A
return A.AddComponent(/datum/component/orbiter, src, radius, clockwise, rotation_speed, rotation_segments, pre_rotation)
/atom/movable/proc/stop_orbit(datum/component/orbiter/orbits)
+ orbit_target = null
return // We're just a simple hook
/atom/proc/transfer_observers_to(atom/target)
diff --git a/code/datums/components/pellet_cloud.dm b/code/datums/components/pellet_cloud.dm
index 6404be94c4..f88b9e1869 100644
--- a/code/datums/components/pellet_cloud.dm
+++ b/code/datums/components/pellet_cloud.dm
@@ -275,6 +275,11 @@
else
target.visible_message("[target] is hit by a [proj_name][hit_part ? " in the [hit_part.name]" : ""]!", null, null, COMBAT_MESSAGE_RANGE, target)
to_chat(target, "You're hit by a [proj_name][hit_part ? " in the [hit_part.name]" : ""]!")
+
+ for(var/M in purple_hearts)
+ var/mob/living/martyr = M
+ if(martyr.stat == DEAD && martyr.client)
+ martyr.client.give_award(/datum/award/achievement/misc/lookoutsir, martyr)
UnregisterSignal(parent, COMSIG_PARENT_PREQDELETED)
if(queued_delete)
qdel(parent)
diff --git a/code/datums/components/remote_materials.dm b/code/datums/components/remote_materials.dm
index ca62a70ee5..b1d23ea3a8 100644
--- a/code/datums/components/remote_materials.dm
+++ b/code/datums/components/remote_materials.dm
@@ -15,13 +15,15 @@ handles linking back and forth.
var/category
var/allow_standalone
var/local_size = INFINITY
+ var/datum/callback/after_insert
-/datum/component/remote_materials/Initialize(category, mapload, allow_standalone = TRUE, force_connect = FALSE)
+/datum/component/remote_materials/Initialize(category, mapload, allow_standalone = TRUE, force_connect = FALSE, _after_insert)
if (!isatom(parent))
return COMPONENT_INCOMPATIBLE
src.category = category
src.allow_standalone = allow_standalone
+ after_insert = _after_insert
RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, .proc/OnAttackBy)
@@ -67,7 +69,7 @@ handles linking back and forth.
/datum/material/plastic,
)
- mat_container = parent.AddComponent(/datum/component/material_container, allowed_mats, local_size, allowed_types=/obj/item/stack)
+ mat_container = parent.AddComponent(/datum/component/material_container, allowed_mats, local_size, allowed_types=/obj/item/stack, _after_insert = after_insert)
/datum/component/remote_materials/proc/set_local_size(size)
local_size = size
@@ -84,38 +86,37 @@ handles linking back and forth.
_MakeLocal()
/datum/component/remote_materials/proc/OnAttackBy(datum/source, obj/item/I, mob/user)
- if (istype(I, /obj/item/multitool))
- var/obj/item/multitool/M = I
- if (!QDELETED(M.buffer) && istype(M.buffer, /obj/machinery/ore_silo))
- if (silo == M.buffer)
+ if(I.tool_behaviour == TOOL_MULTITOOL)
+ if((I.buffer) && istype(I.buffer, /obj/machinery/ore_silo))
+ if(silo == I.buffer)
to_chat(user, "[parent] is already connected to [silo].")
return COMPONENT_NO_AFTERATTACK
- if (silo)
+ if(silo)
silo.connected -= src
silo.updateUsrDialog()
- else if (mat_container)
+ else if(mat_container)
mat_container.retrieve_all()
qdel(mat_container)
- silo = M.buffer
+ silo = I.buffer
silo.connected += src
silo.updateUsrDialog()
mat_container = silo.GetComponent(/datum/component/material_container)
to_chat(user, "You connect [parent] to [silo] from the multitool's buffer.")
return COMPONENT_NO_AFTERATTACK
- else if (silo && istype(I, /obj/item/stack))
- if (silo.remote_attackby(parent, user, I))
+ else if(silo && istype(I, /obj/item/stack))
+ if(silo.remote_attackby(parent, user, I, src))
return COMPONENT_NO_AFTERATTACK
/datum/component/remote_materials/proc/on_hold()
return silo && silo.holds["[get_area(parent)]/[category]"]
/datum/component/remote_materials/proc/silo_log(obj/machinery/M, action, amount, noun, list/mats)
- if (silo)
+ if(silo)
silo.silo_log(M || parent, action, amount, noun, mats)
/datum/component/remote_materials/proc/format_amount()
- if (mat_container)
+ if(mat_container)
return "[mat_container.total_amount] / [mat_container.max_amount == INFINITY ? "Unlimited" : mat_container.max_amount] ([silo ? "remote" : "local"])"
else
return "0 / 0"
diff --git a/code/datums/components/riding.dm b/code/datums/components/riding.dm
index 03f3820a9b..e11eca2975 100644
--- a/code/datums/components/riding.dm
+++ b/code/datums/components/riding.dm
@@ -36,15 +36,15 @@
if(del_on_unbuckle_all && !AM.has_buckled_mobs())
qdel(src)
-/datum/component/riding/proc/vehicle_mob_buckle(datum/source, mob/living/M, force = FALSE)
- handle_vehicle_offsets()
+/datum/component/riding/proc/vehicle_mob_buckle(datum/source, mob/living/M, force)
+ handle_vehicle_offsets(M.buckled?.dir)
-/datum/component/riding/proc/handle_vehicle_layer()
+/datum/component/riding/proc/handle_vehicle_layer(dir)
var/atom/movable/AM = parent
var/static/list/defaults = list(TEXT_NORTH = OBJ_LAYER, TEXT_SOUTH = ABOVE_MOB_LAYER, TEXT_EAST = ABOVE_MOB_LAYER, TEXT_WEST = ABOVE_MOB_LAYER)
- . = defaults["[AM.dir]"]
- if(directional_vehicle_layers["[AM.dir]"])
- . = directional_vehicle_layers["[AM.dir]"]
+ . = defaults["[dir]"]
+ if(directional_vehicle_layers["[dir]"])
+ . = directional_vehicle_layers["[dir]"]
if(isnull(.)) //you can set it to null to not change it.
. = AM.layer
AM.layer = .
@@ -52,12 +52,17 @@
/datum/component/riding/proc/set_vehicle_dir_layer(dir, layer)
directional_vehicle_layers["[dir]"] = layer
-/datum/component/riding/proc/vehicle_moved(datum/source)
+/datum/component/riding/proc/vehicle_moved(datum/source, oldLoc, dir)
+ SIGNAL_HANDLER
+
var/atom/movable/AM = parent
+ if (isnull(dir))
+ dir = AM.dir
+ AM.set_glide_size(DELAY_TO_GLIDE_SIZE(vehicle_move_delay), FALSE)
for(var/i in AM.buckled_mobs)
ride_check(i)
- handle_vehicle_offsets()
- handle_vehicle_layer()
+ handle_vehicle_offsets(dir)
+ handle_vehicle_layer(dir)
/datum/component/riding/proc/ride_check(mob/living/M)
var/atom/movable/AM = parent
@@ -74,9 +79,9 @@
/datum/component/riding/proc/additional_offset_checks()
return TRUE
-/datum/component/riding/proc/handle_vehicle_offsets()
+/datum/component/riding/proc/handle_vehicle_offsets(dir)
var/atom/movable/AM = parent
- var/AM_dir = "[AM.dir]"
+ var/AM_dir = "[dir]"
var/passindex = 0
if(AM.has_buckled_mobs())
for(var/m in AM.buckled_mobs)
@@ -177,8 +182,8 @@
else
last_move_diagonal = FALSE
- handle_vehicle_offsets()
- handle_vehicle_layer()
+ handle_vehicle_offsets(direction)
+ handle_vehicle_layer(direction)
else
to_chat(user, "You'll need the keys in one of your hands to [drive_verb] [AM].")
diff --git a/code/datums/components/rotation.dm b/code/datums/components/rotation.dm
index 129faabdb7..b8d629273b 100644
--- a/code/datums/components/rotation.dm
+++ b/code/datums/components/rotation.dm
@@ -125,7 +125,7 @@
else
if(!default_can_user_rotate(user, default_rotation_direction))
return
- if(istype(I,/obj/item/wrench))
+ if(I.tool_behaviour == TOOL_WRENCH)
BaseRot(user,default_rotation_direction)
return COMPONENT_NO_AFTERATTACK
diff --git a/code/datums/components/squeak.dm b/code/datums/components/squeak.dm
index 0552a791ea..a285b7d3f2 100644
--- a/code/datums/components/squeak.dm
+++ b/code/datums/components/squeak.dm
@@ -19,7 +19,14 @@
/// chance we'll be stopped from squeaking by cooldown when something crossing us squeaks
var/cross_squeak_delay_chance = 33 // about 3 things can squeak at a time
-/datum/component/squeak/Initialize(custom_sounds, volume_override, chance_override, step_delay_override, use_delay_override)
+ ///extra-range for this component's sound
+ var/sound_extra_range = -1
+ ///when sounds start falling off for the squeak
+ var/sound_falloff_distance = SOUND_DEFAULT_FALLOFF_DISTANCE
+ ///sound exponent for squeak. Defaults to 10 as squeaking is loud and annoying enough.
+ var/sound_falloff_exponent = 10
+
+/datum/component/squeak/Initialize(custom_sounds, volume_override, chance_override, step_delay_override, use_delay_override, extrarange, falloff_exponent, fallof_distance)
if(!isatom(parent))
return COMPONENT_INCOMPATIBLE
RegisterSignal(parent, list(COMSIG_ATOM_ENTERED, COMSIG_ATOM_BLOB_ACT, COMSIG_ATOM_HULK_ATTACK, COMSIG_PARENT_ATTACKBY), .proc/play_squeak)
@@ -45,6 +52,12 @@
step_delay = step_delay_override
if(isnum(use_delay_override))
use_delay = use_delay_override
+ if(isnum(extrarange))
+ sound_extra_range = extrarange
+ if(isnum(falloff_exponent))
+ sound_falloff_exponent = falloff_exponent
+ if(isnum(fallof_distance))
+ sound_falloff_distance = fallof_distance
/datum/component/squeak/UnregisterFromParent()
if(!isatom(parent))
@@ -62,6 +75,7 @@
return ..()
/datum/component/squeak/proc/play_squeak()
+ SIGNAL_HANDLER
do_play_squeak()
/datum/component/squeak/proc/do_play_squeak(bypass_cooldown = FALSE)
@@ -69,14 +83,16 @@
return FALSE
if(prob(squeak_chance))
if(!override_squeak_sounds)
- playsound(parent, pickweight(default_squeak_sounds), volume, 1, -1)
+ playsound(parent, pickweight(default_squeak_sounds), volume, TRUE, sound_extra_range, sound_falloff_exponent, falloff_distance = sound_falloff_distance)
else
- playsound(parent, pickweight(override_squeak_sounds), volume, 1, -1)
+ playsound(parent, pickweight(override_squeak_sounds), volume, TRUE, sound_extra_range, sound_falloff_exponent, falloff_distance = sound_falloff_distance)
last_squeak = world.time
return TRUE
return FALSE
/datum/component/squeak/proc/step_squeak()
+ SIGNAL_HANDLER
+
if(steps > step_delay)
do_play_squeak(TRUE)
steps = 0
@@ -84,20 +100,22 @@
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)
return
- else if(istype(AM, /obj/item/projectile))
- var/obj/item/projectile/P = AM
- if(P.original != parent)
- return
+ if(AM.movement_type & (FLYING|FLOATING) || !AM.has_gravity())
+ return
var/atom/current_parent = parent
if(isturf(current_parent.loc))
if(do_play_squeak())
SEND_SIGNAL(AM, COMSIG_CROSS_SQUEAKED)
/datum/component/squeak/proc/use_squeak()
+ SIGNAL_HANDLER
+
if(last_use + use_delay < world.time)
last_use = world.time
play_squeak()
@@ -118,6 +136,8 @@
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/storage/concrete/stack.dm b/code/datums/components/storage/concrete/stack.dm
index a3f1e526a0..d7b118cee4 100644
--- a/code/datums/components/storage/concrete/stack.dm
+++ b/code/datums/components/storage/concrete/stack.dm
@@ -40,7 +40,8 @@
_S.add(can_insert)
S.use(can_insert, TRUE)
return TRUE
- return ..(S.change_stack(null, can_insert), override)
+ I = S.split_stack(null, can_insert)
+ return ..()
/datum/component/storage/concrete/stack/remove_from_storage(obj/item/I, atom/new_location)
var/atom/real_location = real_location()
diff --git a/code/datums/components/storage/concrete/tcg.dm b/code/datums/components/storage/concrete/tcg.dm
new file mode 100644
index 0000000000..a9d8012a4b
--- /dev/null
+++ b/code/datums/components/storage/concrete/tcg.dm
@@ -0,0 +1,49 @@
+/**
+ *A storage component to be used on card piles, for use as hands/decks/discard piles. Don't use on something that's not a card pile!
+ */
+/datum/component/storage/concrete/tcg
+ display_numerical_stacking = FALSE
+ max_w_class = WEIGHT_CLASS_TINY
+ max_items = 30
+ max_combined_w_class = WEIGHT_CLASS_TINY * 30
+ ///The deck that the card pile is using for FAIR PLAY.
+
+/datum/component/storage/concrete/tcg/can_be_inserted(obj/item/I, stop_messages, mob/M)
+ . = ..()
+ return istype(I, /obj/item/tcg_card)
+
+/datum/component/storage/concrete/tcg/PostTransfer()
+ . = ..()
+ handle_empty_deck()
+
+/datum/component/storage/concrete/tcg/remove_from_storage(atom/movable/AM, atom/new_location)
+ . = ..()
+ handle_empty_deck()
+
+/datum/component/storage/concrete/tcg/ui_show(mob/M)
+ . = ..()
+ M.visible_message("[M] starts to look through the contents of \the [parent]!", \
+ "You begin looking into the contents of \the [parent]!")
+
+/datum/component/storage/concrete/tcg/close(mob/M)
+ . = ..()
+ var/list/card_contents = contents()
+ var/obj/temp_parent = parent
+ temp_parent.visible_message("\the [parent] is shuffled after looking through it.")
+ card_contents = shuffle(card_contents)
+
+/datum/component/storage/concrete/tcg/mass_remove_from_storage(atom/target, list/things, datum/progressbar/progress, trigger_on_found)
+ . = ..()
+ if(!things.len)
+ qdel(parent)
+
+/datum/component/storage/concrete/tcg/proc/handle_empty_deck()
+ var/list/contents = contents()
+ //You can't have a deck of one card!
+ if(contents.len == 1)
+ var/obj/item/tcgcard_deck/deck = parent
+ var/obj/item/tcg_card/card = contents[1]
+ remove_from_storage(card, card.drop_location())
+ card.flipped = deck.flipped
+ card.update_icon_state()
+ qdel(parent)
diff --git a/code/datums/components/storage/storage.dm b/code/datums/components/storage/storage.dm
index f93d40bb04..2fa5a20d7a 100644
--- a/code/datums/components/storage/storage.dm
+++ b/code/datums/components/storage/storage.dm
@@ -449,6 +449,10 @@
// this must come before the screen objects only block, dunno why it wasn't before
if(over_object == M)
user_show_to_mob(M)
+ return
+ if(isrevenant(M))
+ RevenantThrow(over_object, M, source)
+ return
if(!M.incapacitated())
if(!istype(over_object, /obj/screen))
dump_content_at(over_object, M)
diff --git a/code/datums/components/tackle.dm b/code/datums/components/tackle.dm
index c69df4f5e3..10ff5bda3d 100644
--- a/code/datums/components/tackle.dm
+++ b/code/datums/components/tackle.dm
@@ -85,6 +85,10 @@
to_chat(user, "You're not ready to tackle!")
return
+ if(!user.mob_has_gravity() ||!user.loc.has_gravity() || isspaceturf(user.loc))
+ to_chat(user, "You can't find your footing without gravity!")
+ return
+
if(user.has_status_effect(STATUS_EFFECT_TASED)) // can't tackle if you just got tased
to_chat(user, "You can't tackle while tased!")
return
diff --git a/code/datums/datum.dm b/code/datums/datum.dm
index d11532a883..42580425ce 100644
--- a/code/datums/datum.dm
+++ b/code/datums/datum.dm
@@ -77,21 +77,21 @@
/**
- * Default implementation of clean-up code.
- *
- * This should be overridden to remove all references pointing to the object being destroyed, if
- * you do override it, make sure to call the parent and return it's return value by default
- *
- * Return an appropriate [QDEL_HINT][QDEL_HINT_QUEUE] to modify handling of your deletion;
- * in most cases this is [QDEL_HINT_QUEUE].
- *
- * The base case is responsible for doing the following
- * * Erasing timers pointing to this datum
- * * Erasing compenents on this datum
- * * Notifying datums listening to signals from this datum that we are going away
- *
- * Returns [QDEL_HINT_QUEUE]
- */
+ * Default implementation of clean-up code.
+ *
+ * This should be overridden to remove all references pointing to the object being destroyed, if
+ * you do override it, make sure to call the parent and return it's return value by default
+ *
+ * Return an appropriate [QDEL_HINT][QDEL_HINT_QUEUE] to modify handling of your deletion;
+ * in most cases this is [QDEL_HINT_QUEUE].
+ *
+ * The base case is responsible for doing the following
+ * * Erasing timers pointing to this datum
+ * * Erasing compenents on this datum
+ * * Notifying datums listening to signals from this datum that we are going away
+ *
+ * Returns [QDEL_HINT_QUEUE]
+ */
/datum/proc/Destroy(force=FALSE, ...)
SHOULD_CALL_PARENT(TRUE)
tag = null
@@ -138,8 +138,6 @@
UnregisterSignal(target, signal_procs[target])
//END: ECS SHIT
- SSsounds.free_datum_channels(src) //?? (not on tg)
-
return QDEL_HINT_QUEUE
#ifdef DATUMVAR_DEBUGGING_MODE
diff --git a/code/datums/datumvars.dm b/code/datums/datumvars.dm
index a91549ab4c..0bb803ddb3 100644
--- a/code/datums/datumvars.dm
+++ b/code/datums/datumvars.dm
@@ -4,7 +4,7 @@
/datum/proc/can_vv_get(var_name)
return TRUE
-/datum/proc/vv_edit_var(var_name, var_value) //called whenever a var is edited
+/datum/proc/vv_edit_var(var_name, var_value, massedit) //called whenever a var is edited
if(var_name == NAMEOF(src, vars))
return FALSE
vars[var_name] = var_value
diff --git a/code/datums/diseases/heart_failure.dm b/code/datums/diseases/heart_failure.dm
index aabb9ed144..952ce4f18d 100644
--- a/code/datums/diseases/heart_failure.dm
+++ b/code/datums/diseases/heart_failure.dm
@@ -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, FALSE, channel = CHANNEL_HEARTBEAT)
sound = TRUE
if(prob(3))
to_chat(H, "You feel a sharp pain in your chest!")
@@ -53,7 +53,7 @@
H.emote("cough")
if(5)
H.stop_sound_channel(CHANNEL_HEARTBEAT)
- H.playsound_local(H, 'sound/effects/singlebeat.ogg', 100, 0)
+ H.playsound_local(H, 'sound/effects/singlebeat.ogg', 100, FALSE)
if(H.stat == CONSCIOUS)
H.visible_message("[H] clutches at [H.p_their()] chest as if [H.p_their()] heart is stopping!")
H.adjustStaminaLoss(60)
diff --git a/code/datums/diseases/transformation.dm b/code/datums/diseases/transformation.dm
index 44775ed031..8dca59ea94 100644
--- a/code/datums/diseases/transformation.dm
+++ b/code/datums/diseases/transformation.dm
@@ -121,7 +121,7 @@
add_monkey(affected_mob.mind)
if(ishuman(affected_mob))
var/mob/living/carbon/monkey/M = affected_mob.monkeyize(TR_KEEPITEMS | TR_KEEPIMPLANTS | TR_KEEPORGANS | TR_KEEPDAMAGE | TR_KEEPVIRUS | TR_KEEPSE)
- M.ventcrawler = VENTCRAWLER_ALWAYS
+ M.AddElement(/datum/element/ventcrawling, given_tier = VENTCRAWLER_ALWAYS)
/datum/disease/transformation/jungle_fever/stage_act()
diff --git a/code/datums/elements/bed_tucking.dm b/code/datums/elements/bed_tucking.dm
index 602c93fab3..4a498b2ed8 100644
--- a/code/datums/elements/bed_tucking.dm
+++ b/code/datums/elements/bed_tucking.dm
@@ -57,4 +57,4 @@
tucked.transform = turn(tucked.transform, -rotation_degree)
- UnregisterSignal(tucked, COMSIG_ITEM_PICKUP)
\ No newline at end of file
+ UnregisterSignal(tucked, COMSIG_ITEM_PICKUP)
diff --git a/code/datums/elements/mob_holder.dm b/code/datums/elements/mob_holder.dm
index d770e0f30d..619f674969 100644
--- a/code/datums/elements/mob_holder.dm
+++ b/code/datums/elements/mob_holder.dm
@@ -141,7 +141,7 @@
/obj/item/clothing/head/mob_holder/dropped(mob/user)
. = ..()
- if(held_mob && isturf(loc))//don't release on soft-drops
+ if(held_mob && !ismob(loc))//don't release on soft-drops
release()
/obj/item/clothing/head/mob_holder/proc/release()
diff --git a/code/datums/elements/ventcrawling.dm b/code/datums/elements/ventcrawling.dm
new file mode 100644
index 0000000000..254345a97f
--- /dev/null
+++ b/code/datums/elements/ventcrawling.dm
@@ -0,0 +1,36 @@
+/datum/element/ventcrawling
+ element_flags = ELEMENT_BESPOKE|ELEMENT_DETACH
+ id_arg_index = 2
+ var/tier
+
+/datum/element/ventcrawling/Attach(datum/target, duration = 0, given_tier = VENTCRAWLER_NUDE)
+ . = ..()
+
+ var/mob/living/person = target
+ if(!istype(person))
+ return FALSE
+
+ src.tier = given_tier
+
+ RegisterSignal(target, COMSIG_HANDLE_VENTCRAWL, .proc/handle_ventcrawl)
+ RegisterSignal(target, COMSIG_CHECK_VENTCRAWL, .proc/check_ventcrawl)
+ to_chat(target, "You can ventcrawl! Use alt+click on vents to quickly travel about the station.")
+
+ if(duration!=0)
+ addtimer(CALLBACK(src, .proc/Detach, target), duration)
+
+/datum/element/ventcrawling/Detach(datum/target)
+ UnregisterSignal(target, list(COMSIG_HANDLE_VENTCRAWL, COMSIG_CHECK_VENTCRAWL))
+ to_chat(target, "You can no longer ventcrawl.")
+
+ return ..()
+
+/datum/element/ventcrawling/proc/handle_ventcrawl(datum/target,atom/A)
+ var/mob/living/person = target
+ if(!istype(person))
+ return FALSE
+
+ person.handle_ventcrawl(A,tier)
+
+/datum/element/ventcrawling/proc/check_ventcrawl()
+ return tier
diff --git a/code/datums/explosion.dm b/code/datums/explosion.dm
index ca65186063..9a29158b33 100644
--- a/code/datums/explosion.dm
+++ b/code/datums/explosion.dm
@@ -33,6 +33,14 @@ GLOBAL_LIST_EMPTY(explosions)
EX_PREPROCESS_EXIT_CHECK\
}
+#define CREAK_DELAY 5 SECONDS //Time taken for the creak to play after explosion, if applicable.
+#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/explosion/New(atom/epicenter, devastation_range, heavy_impact_range, light_impact_range, flash_range, adminlog, ignorecap, flame_range, silent, smoke)
set waitfor = FALSE
@@ -89,7 +97,7 @@ GLOBAL_LIST_EMPTY(explosions)
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)]")
-
+
deadchat_broadcast("An explosion with size ([devastation_range], [heavy_impact_range], [light_impact_range], [flame_range]) has occured at ([get_area(epicenter)])", turf_target = get_turf(epicenter))
var/x0 = epicenter.x
@@ -115,13 +123,14 @@ GLOBAL_LIST_EMPTY(explosions)
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/on_station = SSmapping.level_trait(epicenter.z, ZTRAIT_STATION)
var/creaking_explosion = FALSE
if(prob(devastation_range*30+heavy_impact_range*5) && 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)
+ for(var/MN in GLOB.player_list)
+ var/mob/M = MN
// Double check for client
var/turf/M_turf = get_turf(M)
if(M_turf && M_turf.z == z0)
@@ -131,15 +140,15 @@ GLOBAL_LIST_EMPTY(explosions)
baseshakeamount = sqrt((orig_max_distance - dist)*0.1)
// If inside the blast radius + world.view - 2
if(dist <= round(max_range + world.view - 2, 1))
- M.playsound_local(epicenter, null, 100, 1, frequency, falloff = 5, S = explosion_sound)
+ M.playsound_local(epicenter, null, 100, 1, frequency, 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, 40, 60) // Volume is based on explosion size and 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(75))
+ 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
@@ -147,18 +156,18 @@ GLOBAL_LIST_EMPTY(explosions)
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, 2.5))
-
- else if(M.can_hear() && !isspaceturf(get_turf(M)) && heavy_impact_range) // Big enough explosions echo throughout the hull
+ 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, 2.5))
+ 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(25, 40), 1, frequency, null, null, FALSE, hull_creaking_sound, null, null, null, null, 0), 5 SECONDS)
+ 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
@@ -230,8 +239,13 @@ GLOBAL_LIST_EMPTY(explosions)
atoms += A
for(var/i in atoms)
var/atom/A = i
- if(!QDELETED(A))
- A.ex_act(dist)
+ if(QDELETED(A))
+ continue
+ A.ex_act(dist, null, src)
+ if(QDELETED(A) || !ismovable(A))
+ continue
+ var/atom/movable/AM = A
+ LAZYADD(AM.acted_explosions, explosion_id)
if(flame_dist && prob(40) && !isspaceturf(T) && !T.density)
new /obj/effect/hotspot(T) //Mostly for ambience!
@@ -316,6 +330,14 @@ GLOBAL_LIST_EMPTY(explosions)
++stopped
qdel(src)
+#undef CREAK_DELAY
+#undef FAR_UPPER
+#undef FAR_LOWER
+#undef PROB_SOUND
+#undef SHAKE_CLAMP
+#undef FREQ_UPPER
+#undef FREQ_LOWER
+
#undef EX_PREPROCESS_EXIT_CHECK
#undef EX_PREPROCESS_CHECK_TICK
diff --git a/code/datums/holocall.dm b/code/datums/holocall.dm
index 4bf02e8587..c54f6c971a 100644
--- a/code/datums/holocall.dm
+++ b/code/datums/holocall.dm
@@ -237,7 +237,7 @@
/obj/item/disk/holodisk/Initialize(mapload)
. = ..()
if(preset_record_text)
- build_record()
+ INVOKE_ASYNC(src, .proc/build_record)
/obj/item/disk/holodisk/Destroy()
QDEL_NULL(record)
@@ -425,42 +425,42 @@
"}
/obj/item/disk/holodisk/ruin/snowengieruin
- name = "Blackbox Print-out #EB412"
- desc = "A holodisk containing the last moments of EB412. There's a bloody fingerprint on it."
- preset_image_type = /datum/preset_holoimage/engineer
- preset_record_text = {"
- NAME Dave Tundrale
- SAY Maria, how's Build?
- DELAY 10
- NAME Maria Dell
- PRESET /datum/preset_holoimage/engineer/atmos
- SAY It's fine, don't worry. I've got Plastic on it. And frankly, i'm kinda busy with, the, uhhm, incinerator.
- DELAY 30
- NAME Dave Tundrale
- PRESET /datum/preset_holoimage/engineer
- SAY Aight, wonderful. The science mans been kinda shit though. No RCDs-
- DELAY 20
- NAME Maria Dell
- PRESET /datum/preset_holoimage/engineer/atmos
- SAY Enough about your RCDs. They're not even that important, just bui-
- DELAY 15
- SOUND explosion
- DELAY 10
- SAY Oh, shit!
- DELAY 10
- PRESET /datum/preset_holoimage/engineer/atmos/rig
- LANGUAGE /datum/language/narsie
- NAME Unknown
- SAY RISE, MY LORD!!
- DELAY 10
- LANGUAGE /datum/language/common
- NAME Plastic
- PRESET /datum/preset_holoimage/engineer/rig
- SAY Fuck, fuck, fuck!
- DELAY 20
- SAY It's loose! CALL THE FUCKING SHUTT-
- DELAY 10
- PRESET /datum/preset_holoimage/corgi
- NAME Blackbox Automated Message
- SAY Connection lost. Dumping audio logs to disk.
- DELAY 50"}
+ name = "Blackbox Print-out #EB412"
+ desc = "A holodisk containing the last moments of EB412. There's a bloody fingerprint on it."
+ preset_image_type = /datum/preset_holoimage/engineer
+ preset_record_text = {"
+ NAME Dave Tundrale
+ SAY Maria, how's Build?
+ DELAY 10
+ NAME Maria Dell
+ PRESET /datum/preset_holoimage/engineer/atmos
+ SAY It's fine, don't worry. I've got Plastic on it. And frankly, i'm kinda busy with, the, uhhm, incinerator.
+ DELAY 30
+ NAME Dave Tundrale
+ PRESET /datum/preset_holoimage/engineer
+ SAY Aight, wonderful. The science mans been kinda shit though. No RCDs-
+ DELAY 20
+ NAME Maria Dell
+ PRESET /datum/preset_holoimage/engineer/atmos
+ SAY Enough about your RCDs. They're not even that important, just bui-
+ DELAY 15
+ SOUND explosion
+ DELAY 10
+ SAY Oh, shit!
+ DELAY 10
+ PRESET /datum/preset_holoimage/engineer/atmos/rig
+ LANGUAGE /datum/language/narsie
+ NAME Unknown
+ SAY RISE, MY LORD!!
+ DELAY 10
+ LANGUAGE /datum/language/common
+ NAME Plastic
+ PRESET /datum/preset_holoimage/engineer/rig
+ SAY Fuck, fuck, fuck!
+ DELAY 20
+ SAY It's loose! CALL THE FUCKING SHUTT-
+ DELAY 10
+ PRESET /datum/preset_holoimage/corgi
+ NAME Blackbox Automated Message
+ SAY Connection lost. Dumping audio logs to disk.
+ DELAY 50"}
diff --git a/code/datums/looping_sounds/_looping_sound.dm b/code/datums/looping_sounds/_looping_sound.dm
index 8bee4f3d1c..6af3b3c993 100644
--- a/code/datums/looping_sounds/_looping_sound.dm
+++ b/code/datums/looping_sounds/_looping_sound.dm
@@ -18,8 +18,12 @@
var/list/atom/output_atoms
var/mid_sounds
var/mid_length
+ ///Override for volume of start sound
+ var/start_volume
var/start_sound
var/start_length
+ ///Override for volume of end sound
+ var/end_volume
var/end_sound
var/chance
var/volume = 100
@@ -27,10 +31,9 @@
var/max_loops
var/direct
var/extra_range = 0
- var/falloff
-
+ var/falloff_exponent
var/timerid
- var/init_timerid
+ var/falloff_distance
/datum/looping_sound/New(list/_output_atoms=list(), start_immediately=FALSE, _direct=FALSE)
if(!mid_sounds)
@@ -51,16 +54,13 @@
/datum/looping_sound/proc/start(atom/add_thing)
if(add_thing)
output_atoms |= add_thing
- if(timerid || init_timerid)
+ if(timerid)
return
on_start()
/datum/looping_sound/proc/stop(atom/remove_thing)
if(remove_thing)
output_atoms -= remove_thing
- if(init_timerid)
- deltimer(init_timerid)
- init_timerid = null
if(!timerid)
return
on_stop()
@@ -76,18 +76,18 @@
if(!timerid)
timerid = addtimer(CALLBACK(src, .proc/sound_loop, world.time), mid_length, TIMER_CLIENT_TIME | TIMER_STOPPABLE | TIMER_LOOP)
-/datum/looping_sound/proc/play(soundfile)
+/datum/looping_sound/proc/play(soundfile, volume_override)
var/list/atoms_cache = output_atoms
var/sound/S = sound(soundfile)
if(direct)
S.channel = SSsounds.random_available_channel()
- S.volume = volume
+ S.volume = volume_override || volume //Use volume as fallback if theres no override
for(var/i in 1 to atoms_cache.len)
var/atom/thing = atoms_cache[i]
if(direct)
SEND_SOUND(thing, S)
else
- playsound(thing, S, volume, vary, extra_range, falloff)
+ playsound(thing, S, volume, vary, extra_range, falloff_exponent = falloff_exponent, falloff_distance = falloff_distance)
/datum/looping_sound/proc/get_sound(starttime, _mid_sounds)
. = _mid_sounds || mid_sounds
@@ -97,10 +97,10 @@
/datum/looping_sound/proc/on_start()
var/start_wait = 0
if(start_sound)
- play(start_sound)
+ play(start_sound, start_volume)
start_wait = start_length
- init_timerid = addtimer(CALLBACK(src, .proc/sound_loop), start_wait, TIMER_CLIENT_TIME | TIMER_STOPPABLE)
+ addtimer(CALLBACK(src, .proc/sound_loop), start_wait, TIMER_CLIENT_TIME)
/datum/looping_sound/proc/on_stop()
if(end_sound)
- play(end_sound)
+ play(end_sound, end_volume)
diff --git a/code/datums/looping_sounds/machinery_sounds.dm b/code/datums/looping_sounds/machinery_sounds.dm
index 4f6996bfdd..79810284cd 100644
--- a/code/datums/looping_sounds/machinery_sounds.dm
+++ b/code/datums/looping_sounds/machinery_sounds.dm
@@ -4,7 +4,7 @@
mid_sounds = list('sound/machines/shower/shower_mid1.ogg'=1,'sound/machines/shower/shower_mid2.ogg'=1,'sound/machines/shower/shower_mid3.ogg'=1)
mid_length = 10
end_sound = 'sound/machines/shower/shower_end.ogg'
- volume = 10
+ volume = 20
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -12,6 +12,28 @@
mid_sounds = list('sound/machines/sm/supermatter1.ogg'=1,'sound/machines/sm/supermatter2.ogg'=1,'sound/machines/sm/supermatter3.ogg'=1)
mid_length = 10
volume = 1
+ extra_range = 25
+ falloff_exponent = 10
+ falloff_distance = 5
+ vary = TRUE
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/datum/looping_sound/destabilized_crystal
+ mid_sounds = list('sound/machines/sm/loops/delamming.ogg' = 1)
+ mid_length = 60
+ volume = 55
+ extra_range = 15
+ vary = TRUE
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// /datum/looping_sound/hypertorus
+// mid_sounds = list('sound/machines/hypertorus/loops/hypertorus_nominal.ogg' = 1)
+// mid_length = 60
+// volume = 55
+// extra_range = 15
+// vary = TRUE
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -32,7 +54,22 @@
mid_sounds = list('sound/machines/fryer/deep_fryer_1.ogg' = 1, 'sound/machines/fryer/deep_fryer_2.ogg' = 1)
mid_length = 2
end_sound = 'sound/machines/fryer/deep_fryer_emerge.ogg'
- volume = 5
+ volume = 15
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+/datum/looping_sound/grill
+ mid_sounds = list('sound/machines/grill/grillsizzle.ogg' = 1)
+ mid_length = 18
+ volume = 50
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/datum/looping_sound/deep_fryer
+ mid_length = 2
+ mid_sounds = list('sound/machines/fryer/deep_fryer_1.ogg' = 1, 'sound/machines/fryer/deep_fryer_2.ogg' = 1)
+ volume = 30
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -46,9 +83,39 @@
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-/datum/looping_sound/grill
- mid_length = 2
- mid_sounds = list('sound/machines/fryer/deep_fryer_1.ogg' = 1, 'sound/machines/fryer/deep_fryer_2.ogg' = 1)
- volume = 10
+// /datum/looping_sound/jackpot
+// mid_length = 11
+// mid_sounds = list('sound/machines/roulettejackpot.ogg')
+// volume = 85
+// vary = TRUE
-///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+/*
+/datum/looping_sound/server
+ mid_sounds = list('sound/machines/tcomms/tcomms_mid1.ogg'=1,'sound/machines/tcomms/tcomms_mid2.ogg'=1,'sound/machines/tcomms/tcomms_mid3.ogg'=1,'sound/machines/tcomms/tcomms_mid4.ogg'=1,\
+ 'sound/machines/tcomms/tcomms_mid5.ogg'=1,'sound/machines/tcomms/tcomms_mid6.ogg'=1,'sound/machines/tcomms/tcomms_mid7.ogg'=1)
+ mid_length = 1.8 SECONDS
+ extra_range = -11
+ falloff_distance = 1
+ falloff_exponent = 5
+ volume = 50
+*/
+/datum/looping_sound/computer
+ start_sound = 'sound/machines/computer/computer_start.ogg'
+ start_length = 7.2 SECONDS
+ start_volume = 10
+ mid_sounds = list('sound/machines/computer/computer_mid1.ogg'=1, 'sound/machines/computer/computer_mid2.ogg'=1)
+ mid_length = 1.8 SECONDS
+ end_sound = 'sound/machines/computer/computer_end.ogg'
+ end_volume = 10
+ volume = 2
+ falloff_exponent = 5 //Ultra quiet very fast
+ extra_range = -12
+ falloff_distance = 1 //Instant falloff after initial tile
+
+// /datum/looping_sound/gravgen
+// mid_sounds = list('sound/machines/gravgen/gravgen_mid1.ogg'=1,'sound/machines/gravgen/gravgen_mid2.ogg'=1,'sound/machines/gravgen/gravgen_mid3.ogg'=1,'sound/machines/gravgen/gravgen_mid4.ogg'=1,)
+// mid_length = 1.8 SECONDS
+// extra_range = 10
+// volume = 70
+// falloff_distance = 5
+// falloff_exponent = 20
diff --git a/code/datums/looping_sounds/weather.dm b/code/datums/looping_sounds/weather.dm
index d8ed8d123b..ea34fbb693 100644
--- a/code/datums/looping_sounds/weather.dm
+++ b/code/datums/looping_sounds/weather.dm
@@ -8,7 +8,7 @@
start_sound = 'sound/weather/ashstorm/outside/active_start.ogg'
start_length = 130
end_sound = 'sound/weather/ashstorm/outside/active_end.ogg'
- volume = 80
+ volume = 60
/datum/looping_sound/active_inside_ashstorm
mid_sounds = list(
@@ -20,7 +20,7 @@
start_sound = 'sound/weather/ashstorm/inside/active_start.ogg'
start_length = 130
end_sound = 'sound/weather/ashstorm/inside/active_end.ogg'
- volume = 60
+ volume = 20
/datum/looping_sound/weak_outside_ashstorm
mid_sounds = list(
diff --git a/code/datums/martial/cqc.dm b/code/datums/martial/cqc.dm
index 96ba7bf965..4277cb1316 100644
--- a/code/datums/martial/cqc.dm
+++ b/code/datums/martial/cqc.dm
@@ -97,7 +97,7 @@
D.visible_message("[A] locks [D] into a restraining position!", \
"[A] locks you into a restraining position!")
D.apply_damage(damage, STAMINA)
- D.Stun(100)
+ D.Stun(10)
restraining = TRUE
addtimer(VARSET_CALLBACK(src, restraining, FALSE), 50, TIMER_UNIQUE)
return TRUE
@@ -175,7 +175,7 @@
return TRUE
if(CHECK_MOBILITY(D, MOBILITY_MOVE) || !restraining)
A.do_attack_animation(D, ATTACK_EFFECT_PUNCH)
- if(damage >= stunthreshold)
+ if(damage >= stunthreshold)
I = D.get_active_held_item()
D.visible_message("[A] strikes [D]'s jaw with their hand!", \
"[A] strikes your jaw, disorienting you!")
@@ -196,7 +196,8 @@
log_combat(A, D, "knocked out (Chokehold)(CQC)")
D.visible_message("[A] puts [D] into a chokehold!", \
"[A] puts you into a chokehold!")
- D.SetSleeping(400)
+ if(D.silent <= 10)
+ D.silent = clamp(D.silent + 10, 0, 10)
restraining = FALSE
if(A.grab_state < GRAB_NECK)
A.setGrabState(GRAB_NECK)
@@ -213,7 +214,7 @@
to_chat(usr, "Slam: Grab Harm. Slam opponent into the ground, knocking them down.")
to_chat(usr, "CQC Kick: Harm Harm. Knocks opponent away. Knocks out stunned or knocked down opponents.")
- to_chat(usr, "Restrain: Grab Grab. Locks opponents into a restraining position, disarm to knock them out with a chokehold.")
+ to_chat(usr, "Restrain: Grab Grab. Locks opponents into a restraining position, disarm to mute them with a chokehold.")
to_chat(usr, "Pressure: Disarm Grab. Decent stamina damage.")
to_chat(usr, "Consecutive CQC: Disarm Disarm Harm. Mainly offensive move, huge damage and decent stamina damage.")
diff --git a/code/datums/materials/_material.dm b/code/datums/materials/_material.dm
index 5148aab4ac..658f22a107 100644
--- a/code/datums/materials/_material.dm
+++ b/code/datums/materials/_material.dm
@@ -1,15 +1,24 @@
/*! 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.
+
*/
/datum/material
+ /// What the material is referred to as IC.
var/name = "material"
+ /// A short description of the material. Not used anywhere, yet...
var/desc = "its..stuff."
+ /// What the material is indexed by in the SSmaterials.materials list. Defaults to the type of the material.
+ var/id
+
///Base color of the material, is used for greyscale. Item isn't changed in color if this is null.
var/color
///Base alpha of the material, is used for greyscale icons.
var/alpha
+ ///Bitflags that influence how SSmaterials handles this material.
+ // var/init_flags = MATERIAL_INIT_MAPLOAD
///Materials "Traits". its a map of key = category | Value = Bool. Used to define what it can be used for
var/list/categories = list()
///The type of sheet this material creates. This should be replaced as soon as possible by greyscale sheets
@@ -22,7 +31,7 @@ Simple datum which is instanced once per type and is used for every object of sa
var/value_per_unit = 0
///Armor modifiers, multiplies an items normal armor vars by these amounts.
var/armor_modifiers = list("melee" = 1, "bullet" = 1, "laser" = 1, "energy" = 1, "bomb" = 1, "bio" = 1, "rad" = 1, "fire" = 1, "acid" = 1)
- ///How beautiful is this material per unit?
+ ///How beautiful is this material per unit.
var/beauty_modifier = 0
///Can be used to override the sound items make, lets add some SLOSHing.
var/item_sound_override
@@ -30,14 +39,31 @@ Simple datum which is instanced once per type and is used for every object of sa
var/turf_sound_override
///what texture icon state to overlay
var/texture_layer_icon_state
- ///a cached filter for the texture icon
+ ///a cached icon for the texture filter
var/cached_texture_filter
+ ///What type of shard the material will shatter to
+ var/obj/item/shard_type
+
+
+/** Handles initializing the material.
+ *
+ * Arugments:
+ * - _id: The ID the material should use. Overrides the existing ID.
+ */
+/datum/material/proc/Initialize(_id, ...)
+ if(_id)
+ id = _id
+ else if(isnull(id))
+ id = type
+
+ if(texture_layer_icon_state)
+ cached_texture_filter = icon('icons/materials/composite.dmi', texture_layer_icon_state)
+
+ return TRUE
/datum/material/New()
. = ..()
- if(texture_layer_icon_state)
- var/texture_icon = icon('icons/materials/composite.dmi', texture_layer_icon_state)
- cached_texture_filter = filter(type="layer", icon=texture_icon, blend_mode = BLEND_INSET_OVERLAY)
+ Initialize()
///This proc is called when the material is added to an object.
/datum/material/proc/on_applied(atom/source, amount, material_flags)
@@ -48,18 +74,20 @@ Simple datum which is instanced once per type and is used for every object of sa
source.alpha = alpha
if(texture_layer_icon_state)
ADD_KEEP_TOGETHER(source, MATERIAL_SOURCE(src))
- source.filters += cached_texture_filter
+ source.add_filter("material_texture_[name]",1,layering_filter(icon=cached_texture_filter,blend_mode=BLEND_INSET_OVERLAY))
+ if(alpha < 255)
+ source.opacity = FALSE
if(material_flags & MATERIAL_ADD_PREFIX)
source.name = "[name] [source.name]"
- if(beauty_modifier)
- addtimer(CALLBACK(source, /datum.proc/_AddElement, list(/datum/element/beauty, beauty_modifier * amount)), 0)
+ // if(beauty_modifier) returnign in hardsync2 if i ever port ebeauty cmp
+ // addtimer(CALLBACK(source, /datum.proc/_AddElement, list(/datum/element/beauty, beauty_modifier * amount)), 0)
if(istype(source, /obj)) //objs
on_applied_obj(source, amount, material_flags)
- else if(isturf(source, /turf)) //turfs
+ if(istype(source, /turf)) //turfs
on_applied_turf(source, amount, material_flags)
source.mat_update_desc(src)
@@ -67,8 +95,9 @@ Simple datum which is instanced once per type and is used for every object of sa
///This proc is called when a material updates an object's description
/atom/proc/mat_update_desc(/datum/material/mat)
return
+
///This proc is called when the material is added to an object specifically.
-/datum/material/proc/on_applied_obj(var/obj/o, amount, material_flags)
+/datum/material/proc/on_applied_obj(obj/o, amount, material_flags)
if(material_flags & MATERIAL_AFFECT_STATISTICS)
var/new_max_integrity = CEILING(o.max_integrity * integrity_modifier, 1)
o.modify_max_integrity(new_max_integrity)
@@ -92,43 +121,73 @@ Simple datum which is instanced once per type and is used for every object of sa
I.hitsound = item_sound_override
I.usesound = item_sound_override
I.throwhitsound = item_sound_override
+ // I.mob_throw_hit_sound = item_sound_override
+ // I.equip_sound = item_sound_override
+ // I.pickup_sound = item_sound_override
+ // I.drop_sound = item_sound_override
-/datum/material/proc/on_applied_turf(var/turf/T, amount, material_flags)
+/datum/material/proc/on_applied_turf(turf/T, amount, material_flags)
if(isopenturf(T))
- if(!turf_sound_override)
- return
- var/turf/open/O = T
- O.footstep = turf_sound_override
- O.barefootstep = turf_sound_override
- O.clawfootstep = turf_sound_override
- O.heavyfootstep = turf_sound_override
+ if(turf_sound_override)
+ var/turf/open/O = T
+ O.footstep = turf_sound_override
+ O.barefootstep = turf_sound_override
+ O.clawfootstep = turf_sound_override
+ O.heavyfootstep = turf_sound_override
+ // if(alpha < 255)
+ // T.AddElement(/datum/element/turf_z_transparency, TRUE)
+ return
///This proc is called when the material is removed from an object.
-/datum/material/proc/on_removed(atom/source, material_flags)
+/datum/material/proc/on_removed(atom/source, amount, material_flags)
if(material_flags & MATERIAL_COLOR) //Prevent changing things with pre-set colors, to keep colored toolboxes their looks for example
if(color)
source.remove_atom_colour(FIXED_COLOUR_PRIORITY, color)
- source.alpha = initial(source.alpha)
if(texture_layer_icon_state)
- source.filters -= cached_texture_filter
+ source.remove_filter("material_texture_[name]")
REMOVE_KEEP_TOGETHER(source, MATERIAL_SOURCE(src))
+ source.alpha = initial(source.alpha)
if(material_flags & MATERIAL_ADD_PREFIX)
source.name = initial(source.name)
- if(istype(source, /obj)) //objs
- on_removed_obj(source, material_flags)
+ // if(beauty_modifier) //component/beauty/InheritComponent() will handle the removal.
+ // addtimer(CALLBACK(source, /datum.proc/_AddElement, list(/datum/element/beauty, -beauty_modifier * amount)), 0)
- else if(istype(source, /turf)) //turfs
- on_removed_turf(source, material_flags)
+ if(istype(source, /obj)) //objs
+ on_removed_obj(source, amount, material_flags)
+
+ if(istype(source, /turf)) //turfs
+ on_removed_turf(source, amount, material_flags)
///This proc is called when the material is removed from an object specifically.
-/datum/material/proc/on_removed_obj(obj/o, material_flags)
+/datum/material/proc/on_removed_obj(obj/o, amount, material_flags)
if(material_flags & MATERIAL_AFFECT_STATISTICS)
var/new_max_integrity = initial(o.max_integrity)
o.modify_max_integrity(new_max_integrity)
o.force = initial(o.force)
o.throwforce = initial(o.throwforce)
-/datum/material/proc/on_removed_turf(turf/T, material_flags)
- return
+/datum/material/proc/on_removed_turf(turf/T, amount, material_flags)
+ // if(alpha)
+ // RemoveElement(/datum/element/turf_z_transparency, FALSE)
+
+/**
+ * This proc is called when the mat is found in an item that's consumed by accident. see /obj/item/proc/on_accidental_consumption.
+ * Arguments
+ * * M - person consuming the mat
+ * * S - (optional) item the mat is contained in (NOT the item with the mat itself)
+ */
+/datum/material/proc/on_accidental_mat_consumption(mob/living/carbon/M, obj/item/S)
+ return FALSE
+
+/** Returns the composition of this material.
+ *
+ * Mostly used for alloys when breaking down materials.
+ *
+ * Arguments:
+ * - amount: The amount of the material to break down.
+ * - breakdown_flags: Some flags dictating how exactly this material is being broken down.
+ */
+/datum/material/proc/return_composition(amount=1, breakdown_flags=NONE)
+ return list((src) = amount) // Yes we need the parenthesis, without them BYOND stringifies src into "src" and things break.
diff --git a/code/datums/shuttles.dm b/code/datums/shuttles.dm
index e2e6a05b08..62f8cef5dc 100644
--- a/code/datums/shuttles.dm
+++ b/code/datums/shuttles.dm
@@ -8,12 +8,16 @@
var/description
var/prerequisites
var/admin_notes
-
+ /// How much does this shuttle cost the cargo budget to purchase? Put in terms of CARGO_CRATE_VALUE to properly scale the cost with the current balance of cargo's income.
var/credit_cost = INFINITY
+ /// Can the be legitimately purchased by the station? Used by hardcoded or pre-mapped shuttles like the lavaland or cargo shuttle.
var/can_be_bought = TRUE
+ /// If set, overrides default movement_force on shuttle
+ var/list/movement_force
var/port_x_offset
var/port_y_offset
+ var/extra_desc = ""
/datum/map_template/shuttle/proc/prerequisites_met()
return TRUE
@@ -23,7 +27,7 @@
mappath = "[prefix][shuttle_id].dmm"
. = ..()
-/datum/map_template/shuttle/preload_size(path = mappath, force_cache = FALSE)
+/datum/map_template/shuttle/preload_size(path, force_cache)
. = ..(path, TRUE) // Done this way because we still want to know if someone actualy wanted to cache the map
if(!cached_map)
return
@@ -64,6 +68,9 @@
continue
if(length(place.baseturfs) < 2) // Some snowflake shuttle shit
continue
+ // var/list/sanity = place.baseturfs.Copy() // we do not have new baseturfs yet
+ // sanity.Insert(3, /turf/baseturf_skipover/shuttle)
+ // place.baseturfs = baseturfs_string_list(sanity, place)
place.baseturfs.Insert(3, /turf/baseturf_skipover/shuttle)
for(var/obj/docking_port/mobile/port in place)
@@ -93,6 +100,7 @@
port.dwidth = port_y_offset - 1
port.dheight = width - port_x_offset
+ // these three for loops are cit specific.
for(var/obj/structure/closet/closet in place)
if(closet.anchorable)
closet.anchored = TRUE
@@ -104,11 +112,10 @@
rack.AddComponent(/datum/component/magnetic_catch)
//Whatever special stuff you want
-/datum/map_template/shuttle/proc/post_load(obj/docking_port/mobile/M)
- return
-
-/datum/map_template/shuttle/proc/on_bought()
- return
+/datum/map_template/shuttle/post_load(obj/docking_port/mobile/M)
+ if(movement_force)
+ M.movement_force = movement_force.Copy()
+ M.linkup()
/datum/map_template/shuttle/emergency
port_id = "emergency"
@@ -117,6 +124,7 @@
/datum/map_template/shuttle/cargo
port_id = "cargo"
name = "Base Shuttle Template (Cargo)"
+ can_be_bought = FALSE
/datum/map_template/shuttle/ferry
port_id = "ferry"
@@ -137,10 +145,6 @@
port_id = "mining_common"
can_be_bought = FALSE
-/datum/map_template/shuttle/cargo
- port_id = "cargo"
- can_be_bought = FALSE
-
/datum/map_template/shuttle/arrival
port_id = "arrival"
can_be_bought = FALSE
@@ -189,21 +193,23 @@
name = "Backup Shuttle"
can_be_bought = FALSE
-/datum/map_template/shuttle/emergency/airless
- suffix = "airless"
+/datum/map_template/shuttle/emergency/construction
+ suffix = "construction"
name = "Build your own shuttle kit"
- description = "Save money by building your own shuttle! The chassis will dock upon purchase, but launch will have to be authorized as usual via shuttle call. Interior and lighting not included."
+ description = "For the enterprising shuttle engineer! The chassis will dock upon purchase, but launch will have to be authorized as usual via shuttle call. Comes stocked with construction materials. Unlocks the ability to buy shuttle engine crates from cargo."
admin_notes = "No brig, no medical facilities, just an empty box."
credit_cost = -7500
-/datum/map_template/shuttle/emergency/airless/prerequisites_met()
+/datum/map_template/shuttle/emergency/construction/prerequisites_met()
// first 10 minutes only
return world.time - SSticker.round_start_time < 6000
-/datum/map_template/shuttle/emergency/airless/on_bought()
- //enable buying engines from cargo
- var/datum/supply_pack/P = SSshuttle.supply_packs[/datum/supply_pack/engineering/shuttle_engine]
- P.special_enabled = TRUE
+// this is broken and does not work. Thanks TG
+// /datum/map_template/shuttle/emergency/airless/post_load()
+// . = ..()
+// //enable buying engines from cargo
+// var/datum/supply_pack/P = SSshuttle.supply_packs[/datum/supply_pack/engineering/shuttle_engine]
+// P.special_enabled = TRUE
/datum/map_template/shuttle/emergency/asteroid
@@ -220,6 +226,13 @@
Has medical facilities."
credit_cost = 5000
+// /datum/map_template/shuttle/emergency/pod
+// suffix = "pod"
+// name = "Emergency Pods"
+// description = "We did not expect an evacuation this quickly. All we have available is two escape pods."
+// admin_notes = "For player punishment."
+// can_be_bought = FALSE
+
/datum/map_template/shuttle/emergency/russiafightpit
suffix = "russiafightpit"
name = "Mother Russia Bleeds"
@@ -230,9 +243,10 @@
/datum/map_template/shuttle/emergency/meteor
suffix = "meteor"
name = "Asteroid With Engines Strapped To It"
- description = "A hollowed out asteroid with engines strapped to it. Due to its size and difficulty in steering it, this shuttle may damage the docking area."
+ description = "A hollowed out asteroid with engines strapped to it, the hollowing procedure makes it very difficult to hijack but is very expensive. Due to its size and difficulty in steering it, this shuttle may damage the docking area."
admin_notes = "This shuttle will likely crush escape, killing anyone there."
credit_cost = -5000
+ movement_force = list("KNOCKDOWN" = 3, "THROW" = 2)
/datum/map_template/shuttle/emergency/luxury
suffix = "luxury"
@@ -247,18 +261,30 @@
description = "The glorious results of centuries of plasma research done by Nanotrasen employees. This is the reason why you are here. Get on and dance like you're on fire, burn baby burn!"
admin_notes = "Flaming hot. The main area has a dance machine as well as plasma floor tiles that will be ignited by players every single time."
credit_cost = 10000
+ // can_be_bought = FALSE
-/datum/map_template/shuttle/emergency/arena
- suffix = "arena"
- name = "The Arena"
- 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
+// /datum/map_template/shuttle/emergency/arena
+// suffix = "arena"
+// name = "The Arena"
+// 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("bubblegum" in SSshuttle.shuttle_purchase_requirements_met)
- return TRUE
- return FALSE
+// /datum/map_template/shuttle/emergency/arena/prerequisites_met()
+// return SSshuttle.shuttle_purchase_requirements_met["bubblegum"]
+
+// /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"
@@ -272,6 +298,13 @@
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)!"
@@ -316,7 +349,9 @@
credit_cost = -1000
description = "Due to a lack of functional emergency shuttles, we bought this second hand from a scrapyard and pressed it into service. Please do not lean too heavily on the exterior windows, they are fragile."
admin_notes = "An abomination with no functional medbay, sections missing, and some very fragile windows. Surprisingly airtight."
+ movement_force = list("KNOCKDOWN" = 3, "THROW" = 2)
+// CIT SPECIFIC
/datum/map_template/shuttle/emergency/syndicate
suffix = "syndicate"
name = "Syndicate GM Battlecruiser"
@@ -325,9 +360,7 @@
admin_notes = "An emag exclusive, stocked with syndicate equipment and turrets that will target any simplemob."
/datum/map_template/shuttle/emergency/syndicate/prerequisites_met()
- if("emagged" in SSshuttle.shuttle_purchase_requirements_met)
- return TRUE
- return FALSE
+ return SSshuttle.shuttle_purchase_requirements_met["emagged"]
/datum/map_template/shuttle/emergency/narnar
suffix = "narnar"
@@ -335,6 +368,10 @@
description = "Looks like this shuttle may have wandered into the darkness between the stars on route to the station. Let's not think too hard about where all the bodies came from."
admin_notes = "Contains real cult ruins, mob eyeballs, and inactive constructs. Cult mobs will automatically be sentienced by fun balloon. \
Cloning pods in 'medbay' area are showcases and nonfunctional."
+ credit_cost = 6667 ///The joke is the number so no defines
+
+/datum/map_template/shuttle/emergency/narnar/prerequisites_met()
+ return SSshuttle.shuttle_purchase_requirements_met["narsie"]
/datum/map_template/shuttle/emergency/pubby
suffix = "pubby"
@@ -354,7 +391,7 @@
/datum/map_template/shuttle/emergency/supermatter
suffix = "supermatter"
name = "Hyperfractal Gigashuttle"
- description = "(Emag only) \"I dunno, this seems kinda needlessly complicated.\"\n\
+ description = "\"I dunno, this seems kinda needlessly complicated.\"\n\
\"This shuttle has very a very high safety record, according to CentCom Officer Cadet Yins.\"\n\
\"Are you sure?\"\n\
\"Yes, it has a safety record of N-A-N, which is apparently larger than 100%.\""
@@ -363,19 +400,19 @@
It does, however, still dust anything on contact, emits high levels of radiation, and induce hallucinations in anyone looking at it without protective goggles. \
Emitters spawn powered on, expect admin notices, they are harmless."
credit_cost = 15000
+ movement_force = list("KNOCKDOWN" = 3, "THROW" = 2)
/datum/map_template/shuttle/emergency/supermatter/prerequisites_met()
- if("emagged" in SSshuttle.shuttle_purchase_requirements_met)
- return TRUE
- return FALSE
-
+ return SSshuttle.shuttle_purchase_requirements_met["emagged"]
/datum/map_template/shuttle/emergency/imfedupwiththisworld
suffix = "imfedupwiththisworld"
name = "Oh, Hi Daniel"
description = "How was space work today? Oh, pretty good. We got a new space station and the company will make a lot of money. What space station? I cannot tell you; it's space confidential. \
Aw, come space on. Why not? No, I can't. Anyway, how is your space roleplay life?"
admin_notes = "Tiny, with a single airlock and wooden walls. What could go wrong?"
+ // can_be_bought = FALSE
credit_cost = -5000
+ movement_force = list("KNOCKDOWN" = 3, "THROW" = 2)
/datum/map_template/shuttle/emergency/goon
suffix = "goon"
@@ -383,6 +420,14 @@
description = "The Nanotrasen Emergency Shuttle Port(NES Port for short) is a shuttle used at other less known Nanotrasen facilities and has a more open inside for larger crowds, but fewer onboard shuttle facilities."
credit_cost = 500
+// /datum/map_template/shuttle/emergency/rollerdome
+// suffix = "rollerdome"
+// name = "Uncle Pete's Rollerdome"
+// description = "Developed by a member of Nanotrasen's R&D crew that claims to have travelled from the year 2028.
+// He says this shuttle is based off an old entertainment complex from the 1990s, though our database has no records on anything pertaining to that decade."
+// admin_notes = "ONLY NINETIES KIDS REMEMBER. Uses the fun balloon and drone from the Emergency Bar."
+// credit_cost = 500 * 5
+
/datum/map_template/shuttle/emergency/wabbajack
suffix = "wabbajack"
name = "NT Lepton Violet"
@@ -398,6 +443,7 @@
description = "On the smaller size with a modern design, this shuttle is for the crew who like the cosier things, while still being able to stretch their legs."
credit_cost = 1000
+// CIT SPECIFIC
/datum/map_template/shuttle/emergency/gorilla
suffix = "gorilla"
name = "Gorilla Cargo Freighter"
@@ -405,11 +451,17 @@
credit_cost = 2000
/datum/map_template/shuttle/emergency/gorilla/prerequisites_met()
- if("emagged" in SSshuttle.shuttle_purchase_requirements_met)
- return TRUE
- return FALSE
+ return SSshuttle.shuttle_purchase_requirements_met["emagged"]
-/datum/map_template/shuttle/emergency/cruise
+
+// /datum/map_template/shuttle/emergency/cruise
+// suffix = "cruise"
+// name = "The NTSS Independence"
+// description = "Ordinarily reserved for special functions and events, the Cruise Shuttle Independence can bring a summery cheer to your next station evacuation for a 'modest' fee!"
+// admin_notes = "This motherfucker is BIG. You might need to force dock it."
+// credit_cost = 8000
+
+/datum/map_template/shuttle/emergency/monkey
suffix = "nature"
name = "Dynamic Environmental Interaction Shuttle"
description = "A large shuttle with a center biodome that is flourishing with life. Frolick with the monkeys! (Extra monkeys are stored on the bridge.)"
@@ -441,7 +493,7 @@
/datum/map_template/shuttle/ferry/fancy
suffix = "fancy"
name = "fancy transport ferry"
- description = "At some point, someone upgraded the ferry to have fancier flooring... and less seats."
+ description = "At some point, someone upgraded the ferry to have fancier flooring... and fewer seats."
/datum/map_template/shuttle/ferry/kilo
suffix = "kilo"
@@ -464,6 +516,14 @@
suffix = "cere"
name = "NT Construction Vessel"
+// /datum/map_template/shuttle/whiteship/kilo
+// suffix = "kilo"
+// name = "NT Mining Shuttle"
+
+// /datum/map_template/shuttle/whiteship/donut
+// suffix = "donut"
+// name = "NT Long-Distance Bluespace Jumper"
+
/datum/map_template/shuttle/whiteship/delta
suffix = "delta"
name = "NT Frigate"
@@ -476,10 +536,6 @@
suffix = "cog"
name = "NT Prisoner Transport"
-/datum/map_template/shuttle/cargo/box
- suffix = "box"
- name = "supply shuttle (Box)"
-
/datum/map_template/shuttle/cargo/kilo
suffix = "kilo"
name = "supply shuttle (Kilo)"
@@ -488,6 +544,14 @@
suffix = "birdboat"
name = "supply shuttle (Birdboat)"
+// /datum/map_template/shuttle/cargo/donut
+// suffix = "donut"
+// name = "supply shuttle (Donut)"
+
+// /datum/map_template/shuttle/cargo/pubby
+// suffix = "pubby"
+// name = "supply shuttle (Pubby)"
+
/datum/map_template/shuttle/emergency/delta
suffix = "delta"
name = "Delta Station Emergency Shuttle"
@@ -497,11 +561,24 @@
/datum/map_template/shuttle/emergency/raven
suffix = "raven"
- name = "CentCom Raven Battlecruiser"
- description = "The CentCom Raven Battlecruiser is currently docked at the CentCom ship bay awaiting a mission, this Battlecruiser has been reassigned as an emergency escape shuttle for currently unknown reasons. The CentCom Raven Battlecruiser should comfortably fit a medium to large crew size crew and is complete with all required facitlities including a top of the range CentCom Medical Bay."
- admin_notes = "Comes with turrets that will target any simplemob."
+ name = "CentCom Raven Cruiser"
+ description = "The CentCom Raven Cruiser is a former high-risk salvage vessel, now repurposed into an emergency escape shuttle. \
+ Once first to the scene to pick through warzones for valuable remains, it now serves as an excellent escape option for stations under heavy fire from outside forces. \
+ This escape shuttle boasts shields and numerous anti-personnel turrets guarding its perimeter to fend off meteors and enemy boarding attempts."
+ admin_notes = "Comes with turrets that will target anything without the neutral faction (nuke ops, xenos etc, but not pets)."
credit_cost = 12500
+// /datum/map_template/shuttle/emergency/zeta
+// suffix = "zeta"
+// name = "Tr%nPo2r& Z3TA"
+// description = "A glitch appears on your monitor, flickering in and out of the options laid before you.
+// It seems strange and alien, you may need a special technology to access the signal.."
+// admin_notes = "Has alien surgery tools, and a void core that provides unlimited power."
+// credit_cost = CARGO_CRATE_VALUE * 16
+
+// /datum/map_template/shuttle/emergency/zeta/prerequisites_met()
+// return SSshuttle.shuttle_purchase_requirements_met[SHUTTLE_UNLOCK_ALIENTECH]
+
/datum/map_template/shuttle/emergency/cog
suffix = "cog"
name = "NES Classic"
@@ -524,18 +601,22 @@
suffix = "box"
name = "labour shuttle (Box)"
-/datum/map_template/shuttle/labour/kilo
- suffix = "kilo"
- name = "labour shuttle (Kilo)"
-
/datum/map_template/shuttle/labour/cog
suffix = "cog"
name = "labour shuttle (Cog)"
+// /datum/map_template/shuttle/arrival/donut
+// suffix = "donut"
+// name = "arrival shuttle (Donut)"
+
/datum/map_template/shuttle/infiltrator/basic
suffix = "basic"
name = "basic syndicate infiltrator"
+// /datum/map_template/shuttle/infiltrator/advanced
+// suffix = "advanced"
+// name = "advanced syndicate infiltrator"
+
/datum/map_template/shuttle/cargo/delta
suffix = "delta"
name = "cargo ferry (Delta)"
@@ -548,17 +629,25 @@
suffix = "kilo"
name = "mining shuttle (Kilo)"
+// /datum/map_template/shuttle/mining/large
+// suffix = "large"
+// name = "mining shuttle (Large)"
+
/datum/map_template/shuttle/labour/delta
suffix = "delta"
name = "labour shuttle (Delta)"
+/datum/map_template/shuttle/labour/kilo
+ suffix = "kilo"
+ name = "labour shuttle (Kilo)"
+
/datum/map_template/shuttle/mining_common/meta
suffix = "meta"
name = "lavaland shuttle (Meta)"
-/datum/map_template/shuttle/labour/kilo
- suffix = "kilo"
- name = "labour shuttle (Kilo)"
+// /datum/map_template/shuttle/mining_common/kilo
+// suffix = "kilo"
+// name = "lavaland shuttle (Kilo)"
/datum/map_template/shuttle/arrival/delta
suffix = "delta"
@@ -608,6 +697,18 @@
suffix = "default"
name = "pirate ship (Default)"
+/datum/map_template/shuttle/hunter/space_cop
+ suffix = "space_cop"
+ name = "Police Spacevan"
+
+/datum/map_template/shuttle/hunter/russian
+ suffix = "russian"
+ name = "Russian Cargo Ship"
+
+/datum/map_template/shuttle/hunter/bounty
+ suffix = "bounty"
+ name = "Bounty Hunter Ship"
+
/datum/map_template/shuttle/ruin/caravan_victim
suffix = "caravan_victim"
name = "Small Freighter"
@@ -631,15 +732,3 @@
/datum/map_template/shuttle/snowdin/excavation
suffix = "excavation"
name = "Snowdin Excavation Elevator"
-
-/datum/map_template/shuttle/hunter/space_cop
- suffix = "space_cop"
- name = "Police Spacevan"
-
-/datum/map_template/shuttle/hunter/russian
- suffix = "russian"
- name = "Russian Cargo Ship"
-
-/datum/map_template/shuttle/hunter/bounty
- suffix = "bounty"
- name = "Bounty Hunter Ship"
diff --git a/code/datums/skills/_skill_modifier.dm b/code/datums/skills/_skill_modifier.dm
index c38cbf23c6..fd8de29f28 100644
--- a/code/datums/skills/_skill_modifier.dm
+++ b/code/datums/skills/_skill_modifier.dm
@@ -47,7 +47,7 @@ GLOBAL_LIST_EMPTY(potential_mods_per_skill)
if(!mod_L)
mod_L = GLOB.potential_mods_per_skill[target_skills] = list()
else
- BINARY_INSERT(identifier, mod_L, datum/skill_modifier, src, priority, COMPARE_VALUE)
+ BINARY_INSERT(identifier, mod_L, /datum/skill_modifier, src, priority, COMPARE_VALUE)
mod_L[identifier] = src
GLOB.potential_skills_per_mod[target_skills_key] = list(target_skills)
else //Should be a list.
@@ -66,7 +66,7 @@ GLOBAL_LIST_EMPTY(potential_mods_per_skill)
if(!mod_L)
mod_L = GLOB.potential_mods_per_skill[path] = list()
else
- BINARY_INSERT(identifier, mod_L, datum/skill_modifier, src, priority, COMPARE_VALUE)
+ BINARY_INSERT(identifier, mod_L, /datum/skill_modifier, src, priority, COMPARE_VALUE)
mod_L[identifier] = src
/datum/skill_modifier/Destroy()
diff --git a/code/datums/status_effects/debuffs.dm b/code/datums/status_effects/debuffs.dm
index 19b12410bc..fe59bbe14a 100644
--- a/code/datums/status_effects/debuffs.dm
+++ b/code/datums/status_effects/debuffs.dm
@@ -887,7 +887,7 @@
L.remove_status_effect(STATUS_EFFECT_CHOKINGSTRAND)
-datum/status_effect/pacify
+/datum/status_effect/pacify
id = "pacify"
status_type = STATUS_EFFECT_REPLACE
tick_interval = 1
diff --git a/code/datums/tgs_event_handler.dm b/code/datums/tgs_event_handler.dm
index 731be64183..434450b9be 100644
--- a/code/datums/tgs_event_handler.dm
+++ b/code/datums/tgs_event_handler.dm
@@ -5,8 +5,8 @@
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]
+ var/old_reboot_mode = args[2]
+ var/new_reboot_mode = args[3]
message_admins("TGS: Reboot will no longer [reboot_mode_lookup["[old_reboot_mode]"]], it will instead [reboot_mode_lookup["[new_reboot_mode]"]]")
if(TGS_EVENT_PORT_SWAP)
message_admins("TGS: Changing port from [world.port] to [args[2]]")
@@ -28,12 +28,14 @@
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[old_version.deprefixed_parameter]")
+ 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 c6466fdd96..22a851da1d 100644
--- a/code/datums/traits/_quirk.dm
+++ b/code/datums/traits/_quirk.dm
@@ -11,6 +11,8 @@
var/antag_removal_text // Text will be given to the quirk holder if they get an antag that has it blacklisted.
var/mood_quirk = FALSE //if true, this quirk affects mood and is unavailable if moodlets are disabled
var/mob_trait //if applicable, apply and remove this mob trait
+ /// should we immediately call on_spawn or add a timer to trigger
+ var/on_spawn_immediate = TRUE
var/mob/living/quirk_holder
/datum/quirk/New(mob/living/quirk_mob, spawn_effects)
@@ -26,7 +28,10 @@
START_PROCESSING(SSquirks, src)
add()
if(spawn_effects)
- on_spawn()
+ if(on_spawn_immediate)
+ on_spawn()
+ else
+ addtimer(CALLBACK(src, .proc/on_spawn), 0)
addtimer(CALLBACK(src, .proc/post_add), 30)
/datum/quirk/Destroy()
diff --git a/code/datums/traits/good.dm b/code/datums/traits/good.dm
index 23fd75f982..659149a123 100644
--- a/code/datums/traits/good.dm
+++ b/code/datums/traits/good.dm
@@ -219,3 +219,19 @@
/datum/quirk/night_vision/on_spawn()
var/mob/living/carbon/human/H = quirk_holder
H.update_sight()
+
+/datum/quirk/multilingual
+ name = "Multi-Lingual"
+ desc = "You spent a portion of your life learning to understand an additional language. You may or may not be able to speak it based on your anatomy."
+ value = 1
+ mob_trait = TRAIT_MULTILINGUAL
+ gain_text = "You've learned an extra language!"
+ lose_text = "You've forgotten your extra language."
+
+/datum/quirk/multilingual/post_add()
+ var/mob/living/carbon/human/H = quirk_holder
+ H.grant_language(H.client.prefs.language, TRUE, TRUE, LANGUAGE_MULTILINGUAL)
+
+/datum/quirk/multilingual/remove()
+ var/mob/living/carbon/human/H = quirk_holder
+ H.remove_language(H.client.prefs.language, TRUE, TRUE, LANGUAGE_MULTILINGUAL)
diff --git a/code/datums/traits/negative.dm b/code/datums/traits/negative.dm
index 3cbf4b3cd2..cce138e82c 100644
--- a/code/datums/traits/negative.dm
+++ b/code/datums/traits/negative.dm
@@ -184,6 +184,7 @@ GLOBAL_LIST_EMPTY(family_heirlooms)
gain_text = null // Handled by trauma.
lose_text = null
medical_record_text = "Patient has an untreatable impairment in motor function in the lower extremities."
+ on_spawn_immediate = FALSE
/datum/quirk/paraplegic/add()
var/datum/brain_trauma/severe/paralysis/paraplegic/T = new()
diff --git a/code/datums/traits/neutral.dm b/code/datums/traits/neutral.dm
index 73813fd253..18d565ed5a 100644
--- a/code/datums/traits/neutral.dm
+++ b/code/datums/traits/neutral.dm
@@ -105,6 +105,22 @@
gain_text = "You desire to be hurt."
lose_text = "Pain has become less exciting for you."
+/datum/quirk/libido
+ name = "Nymphomaniac"
+ desc = "You are much more sensitive to arousal."
+ value = 0
+ mob_trait = TRAIT_NYMPHO
+ gain_text = "You are feeling extra wild."
+ lose_text = "You don't feel that burning sensation anymore."
+
+/datum/quirk/libido/add()
+ var/mob/living/carbon/human/H = quirk_holder
+ H.arousal_rate = 3 * initial(H.arousal_rate)
+
+/datum/quirk/libido/remove()
+ var/mob/living/carbon/human/H = quirk_holder
+ H.arousal_rate = initial(H.arousal_rate)
+
/datum/quirk/alcohol_intolerance
name = "Alcohol Intolerance"
desc = "You take toxin damage from alcohol rather than getting drunk."
diff --git a/code/datums/view.dm b/code/datums/view.dm
index 8eb06c2bd2..5610fb040e 100644
--- a/code/datums/view.dm
+++ b/code/datums/view.dm
@@ -83,8 +83,6 @@
/datum/viewData/proc/apply()
chief.change_view(getView())
safeApplyFormat()
- if(chief.prefs.auto_fit_viewport)
- chief.fit_viewport()
/datum/viewData/proc/supress()
is_suppressed = TRUE
diff --git a/code/datums/weather/weather_types/radiation_storm.dm b/code/datums/weather/weather_types/radiation_storm.dm
index 4638508d1c..337be3e005 100644
--- a/code/datums/weather/weather_types/radiation_storm.dm
+++ b/code/datums/weather/weather_types/radiation_storm.dm
@@ -22,7 +22,7 @@
target_trait = ZTRAIT_STATION
immunity_type = "rad"
-
+
var/radiation_intensity = 100
/datum/weather/rad_storm/telegraph()
diff --git a/code/datums/wires/_wires.dm b/code/datums/wires/_wires.dm
index 68c475f67b..1c9c14ee3e 100644
--- a/code/datums/wires/_wires.dm
+++ b/code/datums/wires/_wires.dm
@@ -265,11 +265,10 @@
reveal_wires = TRUE
// Same for anyone with an abductor multitool.
- else if(user.is_holding_item_of_type(/obj/item/multitool/abductor))
- reveal_wires = TRUE
- // and advanced multitool
- else if(user.is_holding_item_of_type(/obj/item/multitool/advanced))
- reveal_wires = TRUE
+ else if(user.is_holding_tool_quality(TOOL_MULTITOOL))
+ var/obj/item/tool = user.is_holding_tool_quality(TOOL_MULTITOOL)
+ if(tool.show_wires)
+ reveal_wires = TRUE
// Station blueprints do that too, but only if the wires are not randomized.
else if(user.is_holding_item_of_type(/obj/item/areaeditor/blueprints) && !randomize)
diff --git a/code/datums/world_topic.dm b/code/datums/world_topic.dm
index 7a1f0f6980..946090c571 100644
--- a/code/datums/world_topic.dm
+++ b/code/datums/world_topic.dm
@@ -90,7 +90,7 @@
if(!is_new_ckey)
log_admin("AUTO BUNKER: [ckeytobypass] given access (incoming comms from [sender]).")
message_admins("AUTO BUNKER: [ckeytobypass] given access (incoming comms from [sender]).")
- send2irc("Panic Bunker", "AUTO BUNKER: [ckeytobypass] given access (incoming comms from [sender]).")
+ send2adminchat("Panic Bunker", "AUTO BUNKER: [ckeytobypass] given access (incoming comms from [sender]).")
return "Success"
/datum/world_topic/ahelp_relay
@@ -106,8 +106,8 @@
/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()
+ for(var/obj/machinery/computer/communications/console in GLOB.machines)
+ console.override_cooldown()
/datum/world_topic/news_report
keyword = "News_Report"
diff --git a/code/datums/wounds/_wounds.dm b/code/datums/wounds/_wounds.dm
index 02421b1e52..9c5a41de5d 100644
--- a/code/datums/wounds/_wounds.dm
+++ b/code/datums/wounds/_wounds.dm
@@ -134,6 +134,7 @@
if(status_effect_type)
linked_status_effect = victim.apply_status_effect(status_effect_type, src)
SEND_SIGNAL(victim, COMSIG_CARBON_GAIN_WOUND, src, limb)
+ victim.emote("pain")
if(!victim.alerts["wound"]) // only one alert is shared between all of the wounds
victim.throw_alert("wound", /obj/screen/alert/status_effect/wound)
diff --git a/code/game/alternate_appearance.dm b/code/game/alternate_appearance.dm
index d7c34da34a..a1746001b9 100644
--- a/code/game/alternate_appearance.dm
+++ b/code/game/alternate_appearance.dm
@@ -168,7 +168,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)
diff --git a/code/game/area/Space_Station_13_areas.dm b/code/game/area/Space_Station_13_areas.dm
index 2c2cc46344..b2673c960e 100644
--- a/code/game/area/Space_Station_13_areas.dm
+++ b/code/game/area/Space_Station_13_areas.dm
@@ -45,6 +45,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
ambientsounds = SPACE
blob_allowed = FALSE //Eating up space doesn't count for victory as a blob.
considered_hull_exterior = TRUE
+ sound_environment = SOUND_AREA_SPACE
/area/space/nearstation
icon_state = "space_near"
@@ -70,6 +71,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
blob_allowed = FALSE //Nope, no winning on the asteroid as a blob. Gotta eat the station.
valid_territory = FALSE
ambientsounds = MINING
+ sound_environment = SOUND_AREA_ASTEROID
/area/asteroid/nearstation
dynamic_lighting = DYNAMIC_LIGHTING_FORCED
@@ -107,7 +109,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
/area/maintenance
ambientsounds = MAINTENANCE
valid_territory = FALSE
-
+ sound_environment = SOUND_AREA_TUNNEL_ENCLOSED
//Departments
@@ -122,6 +124,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"
@@ -265,6 +268,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
/area/hallway
nightshift_public_area = NIGHTSHIFT_AREA_PUBLIC
+ sound_environment = SOUND_AREA_STANDARD_STATION
/area/hallway/primary/aft
name = "Aft Primary Hallway"
@@ -333,30 +337,36 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
name = "Bridge"
icon_state = "bridge"
music = "signal"
+ sound_environment = SOUND_AREA_STANDARD_STATION
/area/bridge/meeting_room
name = "Heads of Staff Meeting Room"
icon_state = "meeting"
music = null
+ sound_environment = SOUND_AREA_MEDIUM_SOFTFLOOR
/area/bridge/meeting_room/council
name = "Council Chamber"
icon_state = "meeting"
music = null
+ sound_environment = SOUND_AREA_MEDIUM_SOFTFLOOR
/area/bridge/showroom/corporate
name = "Corporate Showroom"
icon_state = "showroom"
music = null
+ sound_environment = SOUND_AREA_MEDIUM_SOFTFLOOR
/area/crew_quarters/heads/captain
name = "Captain's Office"
icon_state = "captain"
clockwork_warp_allowed = FALSE
+ 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"
@@ -401,10 +411,12 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
/area/comms
name = "Communications Relay"
icon_state = "tcomsatcham"
+ sound_environment = SOUND_AREA_STANDARD_STATION
/area/server
name = "Messaging Server Room"
icon_state = "server"
+ sound_environment = SOUND_AREA_STANDARD_STATION
//Crew
@@ -413,6 +425,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
icon_state = "Sleep"
safe = TRUE
nightshift_public_area = NIGHTSHIFT_AREA_RECREATION
+ sound_environment = SOUND_AREA_STANDARD_STATION
/area/crew_quarters/dorms/male
name = "Male Dorm"
@@ -431,6 +444,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
/area/crew_quarters/toilet
name = "Dormitory Toilets"
icon_state = "toilet"
+ sound_environment = SOUND_AREA_SMALL_ENCLOSED
/area/crew_quarters/toilet/auxiliary
name = "Auxiliary Restrooms"
@@ -465,6 +479,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
name = "Lounge"
icon_state = "yellow"
nightshift_public_area = NIGHTSHIFT_AREA_RECREATION
+ sound_environment = SOUND_AREA_SMALL_SOFTFLOOR
/area/crew_quarters/arcade
name = "Arcade"
@@ -502,15 +517,18 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
/area/crew_quarters/kitchen/backroom
name = "Kitchen Coldroom"
icon_state = "kitchen"
+ sound_environment = SOUND_AREA_SMALL_ENCLOSED
/area/crew_quarters/bar
name = "Bar"
icon_state = "bar"
nightshift_public_area = NIGHTSHIFT_AREA_RECREATION
+ sound_environment = SOUND_AREA_WOODFLOOR
/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"
@@ -526,6 +544,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"
@@ -546,10 +565,12 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
icon_state = "library"
flags_1 = NONE
nightshift_public_area = NIGHTSHIFT_AREA_RECREATION
+ sound_environment = SOUND_AREA_LARGE_SOFTFLOOR
/area/library/lounge
name = "Library Lounge"
icon_state = "library"
+ sound_environment = SOUND_AREA_SMALL_SOFTFLOOR
/area/library/abandoned
name = "Abandoned Library"
@@ -564,6 +585,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
clockwork_warp_allowed = FALSE
clockwork_warp_fail = "The consecration here prevents you from warping in."
nightshift_public_area = NIGHTSHIFT_AREA_RECREATION
+ sound_environment = SOUND_AREA_LARGE_ENCLOSED
/area/chapel/main
name = "Chapel"
@@ -579,6 +601,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"
@@ -590,12 +613,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
ambientsounds = ENGINEERING
+ sound_environment = SOUND_AREA_LARGE_ENCLOSED
/area/engine/engine_smes
name = "Engineering SMES"
@@ -613,14 +638,17 @@ 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"
+ sound_environment = SOUND_AREA_LARGE_ENCLOSED
/area/engine/supermatter
name = "Supermatter Engine"
icon_state = "engine_sm"
+ sound_environment = SOUND_AREA_SMALL_ENCLOSED
/area/engine/break_room
name = "Engineering Foyer"
icon_state = "engine_foyer"
+ sound_environment = SOUND_AREA_SMALL_ENCLOSED
/area/engine/gravity_generator
name = "Gravity Generator Room"
@@ -635,6 +663,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"
@@ -654,10 +683,12 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
blob_allowed = FALSE
flags_1 = NONE
ambientsounds = ENGINEERING
+ 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"
@@ -763,6 +794,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
icon_state = "gateway"
music = "signal"
ambientsounds = ENGINEERING
+ sound_environment = SOUND_AREA_STANDARD_STATION
//MedBay
@@ -770,6 +802,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
name = "Medical"
icon_state = "medbay3"
ambientsounds = MEDICAL
+ sound_environment = SOUND_AREA_STANDARD_STATION
/area/medical/clinic
name = "Clinic"
@@ -780,6 +813,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
name = "Abandoned Medbay"
icon_state = "medbay3"
music = 'sound/ambience/signal.ogg'
+ sound_environment = SOUND_AREA_SMALL_ENCLOSED
/area/medical/medbay/central
name = "Medbay Central"
@@ -821,6 +855,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"
@@ -839,6 +874,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
name = "Morgue"
icon_state = "morgue"
ambientsounds = SPOOKY
+ sound_environment = SOUND_AREA_SMALL_ENCLOSED
/area/medical/chemistry
name = "Chemistry"
@@ -879,6 +915,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
name = "Security"
icon_state = "security"
ambientsounds = HIGHSEC
+ sound_environment = SOUND_AREA_STANDARD_STATION
/area/security/main
name = "Security Office"
@@ -891,6 +928,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
/area/security/courtroom
name = "Courtroom"
icon_state = "courtroom"
+ sound_environment = SOUND_AREA_LARGE_ENCLOSED
/area/security/prison
name = "Prison Wing"
@@ -903,10 +941,12 @@ 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/armory
name = "Armory"
@@ -920,6 +960,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
/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"
@@ -997,18 +1038,17 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
/area/quartermaster
name = "Quartermasters"
icon_state = "quart"
-
-///////////WORK IN PROGRESS//////////
+ 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"
-
-////////////WORK IN PROGRESS//////////
+ sound_environment = SOUND_AREA_LARGE_ENCLOSED
/area/quartermaster/office
name = "Cargo Office"
@@ -1017,6 +1057,7 @@ 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"
@@ -1046,6 +1087,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
name = "Custodial Closet"
icon_state = "janitor"
flags_1 = NONE
+ sound_environment = SOUND_AREA_SMALL_ENCLOSED
/area/janitor/aux
name = "Auxiliary Custodial Closet"
@@ -1055,6 +1097,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
/area/hydroponics
name = "Hydroponics"
icon_state = "hydro"
+ sound_environment = SOUND_AREA_STANDARD_STATION
/area/hydroponics/lobby
name = "Hydroponics Lobby"
@@ -1067,6 +1110,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"
@@ -1077,6 +1121,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
/area/science
name = "Science Division"
icon_state = "toxlab"
+ sound_environment = SOUND_AREA_STANDARD_STATION
/area/science/observatory
name = "Research Observatory"
@@ -1167,12 +1212,15 @@ 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"
icon_state = "toxmisc"
//Storage
+/area/storage
+ sound_environment = SOUND_AREA_STANDARD_STATION
/area/storage/tools
name = "Auxiliary Tool Storage"
@@ -1242,6 +1290,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
name = "Construction Area"
icon_state = "yellow"
ambientsounds = ENGINEERING
+ sound_environment = SOUND_AREA_STANDARD_STATION
/area/construction/minisat_exterior
name = "Minisat Exterior"
@@ -1250,6 +1299,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
/area/construction/mining/aux_base
name = "Auxiliary Base Construction"
icon_state = "yellow"
+ sound_environment = SOUND_AREA_MEDIUM_SOFTFLOOR
/area/construction/mining/aux_base/closet
name = "Auxiliary Closet Construction"
@@ -1305,6 +1355,8 @@ 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"
@@ -1329,10 +1381,12 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
/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"
@@ -1341,6 +1395,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"
@@ -1365,6 +1420,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/AIsatextFP
name = "AI Sat Ext"
@@ -1418,6 +1474,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
/area/tcommsat/computer
name = "Telecomms Control Room"
icon_state = "tcomsatcomp"
+ sound_environment = SOUND_AREA_MEDIUM_SOFTFLOOR
/area/tcommsat/server
name = "Telecomms Server Room"
diff --git a/code/game/area/ai_monitored.dm b/code/game/area/ai_monitored.dm
index 558a4b1026..48e77e1623 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)
. = ..()
diff --git a/code/game/area/areas.dm b/code/game/area/areas.dm
index ff177898cb..4790d20f64 100644
--- a/code/game/area/areas.dm
+++ b/code/game/area/areas.dm
@@ -1,16 +1,72 @@
-// Areas.dm
-
-
+/**
+ * # area
+ *
+ * A grouping of tiles into a logical space, mostly used by map editors
+ */
/area
- level = null
name = "Space"
icon = 'icons/turf/areas.dmi'
icon_state = "unknown"
layer = AREA_LAYER
- plane = BLACKNESS_PLANE //Keeping this on the default plane, GAME_PLANE, will make area overlays fail to render on FLOOR_PLANE.
+ //Keeping this on the default plane, GAME_PLANE, will make area overlays fail to render on FLOOR_PLANE.
+ plane = BLACKNESS_PLANE
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
invisibility = INVISIBILITY_LIGHTING
+ var/fire = null
+ ///Whether there is an atmos alarm in this area
+ var/atmosalm = FALSE
+ var/poweralm = FALSE
+ var/lightswitch = TRUE
+
+ /// All beauty in this area combined, only includes indoor area.
+ var/totalbeauty = 0
+ /// Beauty average per open turf in the area
+ var/beauty = 0
+ /// If a room is too big it doesn't have beauty.
+ var/beauty_threshold = 150
+
+ /// 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
+
+ /// Size of the area in open turfs, only calculated for indoors areas.
+ var/areasize = 0
+
+ /// Bonus mood for being in this area
+ var/mood_bonus = 0
+ /// Mood message for being here, only shows up if mood_bonus != 0
+ var/mood_message = "This area is pretty nice!\n"
+
+ ///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 = FALSE
+
+ var/parallax_movedir = 0
+
+ var/list/ambientsounds = GENERIC
+ flags_1 = CAN_BE_DIRTY_1
+
+ var/list/firedoors
+ var/list/cameras
+ var/list/firealarms
+ var/firedoors_last_closed_on = 0
+
+
+ ///This datum, if set, allows terrain generation behavior to be ran on Initialize()
+ // var/datum/map_generator/map_generator
+
+ ///Used to decide what kind of reverb the area makes sound have
+ var/sound_environment = SOUND_ENVIRONMENT_NONE
+
+ /// CIT SPECIFIC VARS
+
/// Set in New(); preserves the name set by the map maker, even if renamed by the Blueprints.
var/map_name
@@ -41,29 +97,8 @@
/// Considered space for hull shielding
var/considered_hull_exterior = FALSE
- var/fire = null
var/atmos = TRUE
- var/atmosalm = FALSE
- var/poweralm = TRUE
- var/lightswitch = TRUE
- var/totalbeauty = 0 //All beauty in this area combined, only includes indoor area.
- var/beauty = 0 // Beauty average per open turf in the area
- var/beauty_threshold = 150 //If a room is too big it doesn't have beauty.
-
- var/requires_power = TRUE
- /// This gets overridden to 1 for space in area/Initialize().
- var/always_unpowered = FALSE
-
- /// For space, the asteroid, lavaland, etc. Used with blueprints to determine if we are adding a new area (vs editing a station room)
- var/outdoors = FALSE
-
- /// Size of the area in open turfs, only calculated for indoors areas.
- var/areasize = 0
-
- var/power_equip = TRUE
- var/power_light = TRUE
- var/power_environ = TRUE
var/music = null
var/used_equip = 0
var/used_light = 0
@@ -72,7 +107,6 @@
var/static_light = 0
var/static_environ
- var/has_gravity = 0
/// Are you forbidden from teleporting to the area? (centcom, mobs, wizard, hand teleporter)
var/noteleport = FALSE
/// Hides area from player Teleport function.
@@ -84,15 +118,6 @@
var/no_air = null
- var/parallax_movedir = 0
-
- var/list/ambientsounds = GENERIC
- flags_1 = CAN_BE_DIRTY_1
-
- var/list/firedoors
- var/list/cameras
- var/list/firealarms
- var/firedoors_last_closed_on = 0
var/xenobiology_compatible = FALSE //Can the Xenobio management console transverse this area by default?
var/list/canSmoothWithAreas //typecache to limit the areas that atoms in this area can smooth with
@@ -111,10 +136,24 @@
var/nightshift_public_area = NIGHTSHIFT_AREA_NONE //considered a public area for nightshift
-/*Adding a wizard area teleport list because motherfucking lag -- Urist*/
-/*I am far too lazy to make it a proper list of areas so I'll just make it run the usual telepot routine at the start of the game*/
+
+/**
+ * A list of teleport locations
+ *
+ * Adding a wizard area teleport list because motherfucking lag -- Urist
+ * I am far too lazy to make it a proper list of areas so I'll just make it run the usual telepot routine at the start of the game
+ */
GLOBAL_LIST_EMPTY(teleportlocs)
+/**
+ * Generate a list of turfs you can teleport to from the areas list
+ *
+ * Includes areas if they're not a shuttle or not not teleport or have no contents
+ *
+ * The chosen turf is the first item in the areas contents that is a station level
+ *
+ * The returned list of turfs is sorted by name
+ */
/proc/process_teleport_locs()
for(var/V in GLOB.sortedAreas)
var/area/AR = V
@@ -128,11 +167,19 @@ GLOBAL_LIST_EMPTY(teleportlocs)
if (picked && is_station_level(picked.z))
GLOB.teleportlocs[AR.name] = AR
- sortTim(GLOB.teleportlocs, /proc/cmp_text_dsc)
-
-// ===
+ sortTim(GLOB.teleportlocs, /proc/cmp_text_asc)
+/**
+ * Called when an area loads
+ *
+ * Adds the item to the GLOB.areas_by_type list based on area type
+ */
/area/New()
+ // This interacts with the map loader, so it needs to be set immediately
+ // rather than waiting for atoms to initialize.
+ if (unique)
+ GLOB.areas_by_type[type] = src
+
if(!minimap_color) // goes in New() because otherwise it doesn't fucking work
// generate one using the icon_state
if(icon_state && icon_state != "unknown")
@@ -141,14 +188,18 @@ GLOBAL_LIST_EMPTY(teleportlocs)
minimap_color = I.GetPixel(1,1)
else // no icon state? use random.
minimap_color = rgb(rand(50,70),rand(50,70),rand(50,70)) // This interacts with the map loader, so it needs to be set immediately
- // rather than waiting for atoms to initialize.
- if (unique)
- GLOB.areas_by_type[type] = src
return ..()
+/**
+ * Initalize this area
+ *
+ * intializes the dynamic area lighting and also registers the area with the z level via
+ * reg_in_areas_in_z
+ *
+ * returns INITIALIZE_HINT_LATELOAD
+ */
/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)
@@ -202,29 +253,55 @@ GLOBAL_LIST_EMPTY(teleportlocs)
return INITIALIZE_HINT_LATELOAD
+/**
+ * Sets machine power levels in the area
+ */
/area/LateInitialize()
if(!base_area) //we don't want to run it twice.
power_change() // all machines set to current power level, also updates icon
update_beauty()
-/area/proc/reg_in_areas_in_z()
- if(contents.len)
- var/list/areas_in_z = SSmapping.areas_in_z
- var/z
- update_areasize()
- for(var/i in 1 to contents.len)
- var/atom/thing = contents[i]
- if(!thing)
- continue
- z = thing.z
- break
- if(!z)
- WARNING("No z found for [src]")
- return
- if(!areas_in_z["[z]"])
- areas_in_z["[z]"] = list()
- areas_in_z["[z]"] += src
+/// Soon ™
+/area/proc/RunGeneration()
+ // if(map_generator)
+ // map_generator = new map_generator()
+ // var/list/turfs = list()
+ // for(var/turf/T in contents)
+ // turfs += T
+ // map_generator.generate_terrain(turfs)
+/area/proc/test_gen()
+ // if(map_generator)
+ // var/list/turfs = list()
+ // for(var/turf/T in contents)
+ // turfs += T
+ // map_generator.generate_terrain(turfs)
+
+/**
+ * Register this area as belonging to a z level
+ *
+ * Ensures the item is added to the SSmapping.areas_in_z list for this z
+ */
+/area/proc/reg_in_areas_in_z()
+ if(!length(contents))
+ return
+ var/list/areas_in_z = SSmapping.areas_in_z
+ update_areasize()
+ if(!z)
+ WARNING("No z found for [src]")
+ return
+ if(!areas_in_z["[z]"])
+ areas_in_z["[z]"] = list()
+ areas_in_z["[z]"] += src
+
+/**
+ * Destroy an area and clean it up
+ *
+ * Removes the area from GLOB.areas_by_type and also stops it processing on SSobj
+ *
+ * This is despite the fact that no code appears to put it on SSobj, but
+ * who am I to argue with old coders
+ */
/area/Destroy()
if(GLOB.areas_by_type[type] == src)
GLOB.areas_by_type[type] = null
@@ -244,6 +321,11 @@ GLOBAL_LIST_EMPTY(teleportlocs)
STOP_PROCESSING(SSobj, src)
return ..()
+/**
+ * Generate a power alert for this area
+ *
+ * Sends to all ai players, alert consoles, drones and alarm monitor programs in the world
+ */
/area/proc/poweralert(state, obj/source)
if (state != poweralm)
poweralm = state
@@ -525,6 +607,13 @@ GLOBAL_LIST_EMPTY(teleportlocs)
used_environ += amount
+/**
+ * Call back when an atom enters an area
+ *
+ * Sends signals COMSIG_AREA_ENTERED and COMSIG_ENTER_AREA (to the atom)
+ *
+ * If the area has ambience, then it plays some ambience music to the ambience channel
+ */
/area/Entered(atom/movable/M, atom/OldLoc)
set waitfor = FALSE
SEND_SIGNAL(src, COMSIG_AREA_ENTERED, M)
@@ -567,6 +656,12 @@ GLOBAL_LIST_EMPTY(teleportlocs)
return FALSE //Too big
beauty = totalbeauty / areasize
+
+/**
+ * Called when an atom exits an area
+ *
+ * Sends signals COMSIG_AREA_EXITED and COMSIG_EXIT_AREA (to the atom)
+ */
/area/Exited(atom/movable/M)
SEND_SIGNAL(src, COMSIG_AREA_EXITED, M)
SEND_SIGNAL(M, COMSIG_EXIT_AREA, src) //The atom that exits the area
diff --git a/code/game/area/areas/away_content.dm b/code/game/area/areas/away_content.dm
index 27a73ae5f1..63beaec412 100644
--- a/code/game/area/areas/away_content.dm
+++ b/code/game/area/areas/away_content.dm
@@ -9,6 +9,7 @@ Unused icons for new areas are "awaycontent1" ~ "awaycontent30"
icon_state = "away"
has_gravity = STANDARD_GRAVITY
ambientsounds = AWAY_MISSION
+ sound_environment = SOUND_ENVIRONMENT_ROOM
/area/awaymission/beach
name = "Beach"
diff --git a/code/game/area/areas/holodeck.dm b/code/game/area/areas/holodeck.dm
index 9eec00460a..c0819d29b9 100644
--- a/code/game/area/areas/holodeck.dm
+++ b/code/game/area/areas/holodeck.dm
@@ -4,6 +4,7 @@
dynamic_lighting = DYNAMIC_LIGHTING_DISABLED
flags_1 = 0
hidden = TRUE
+ sound_environment = SOUND_ENVIRONMENT_PADDED_CELL
var/obj/machinery/computer/holodeck/linked
var/restricted = 0 // if true, program goes on emag list
diff --git a/code/game/area/areas/mining.dm b/code/game/area/areas/mining.dm
index 70e0910dde..520c7ba59f 100644
--- a/code/game/area/areas/mining.dm
+++ b/code/game/area/areas/mining.dm
@@ -19,6 +19,7 @@
flags_1 = NONE
ambientsounds = MINING
flora_allowed = FALSE
+ sound_environment = SOUND_AREA_STANDARD_STATION
/area/mine/unexplored
name = "Mine"
@@ -86,6 +87,7 @@
has_gravity = STANDARD_GRAVITY
flags_1 = NONE
flora_allowed = TRUE
+ sound_environment = SOUND_AREA_LAVALAND
/area/lavaland/surface
name = "Lavaland"
@@ -139,6 +141,7 @@
flags_1 = NONE
flora_allowed = TRUE
blob_allowed = FALSE
+ sound_environment = SOUND_AREA_ICEMOON
/area/icemoon/surface
name = "Icemoon"
diff --git a/code/game/area/areas/ruins/_ruins.dm b/code/game/area/areas/ruins/_ruins.dm
index b97c3f0ef4..17ba4f7721 100644
--- a/code/game/area/areas/ruins/_ruins.dm
+++ b/code/game/area/areas/ruins/_ruins.dm
@@ -7,6 +7,7 @@
hidden = TRUE
dynamic_lighting = DYNAMIC_LIGHTING_FORCED
ambientsounds = RUINS
+ sound_environment = SOUND_ENVIRONMENT_STONEROOM
/area/ruin/unpowered
diff --git a/code/game/area/areas/shuttles.dm b/code/game/area/areas/shuttles.dm
index e7f8c63d4a..9a890ead75 100644
--- a/code/game/area/areas/shuttles.dm
+++ b/code/game/area/areas/shuttles.dm
@@ -12,6 +12,7 @@
icon_state = "shuttle"
// Loading the same shuttle map at a different time will produce distinct area instances.
unique = FALSE
+ sound_environment = SOUND_ENVIRONMENT_ROOM
/area/shuttle/Initialize()
if(!canSmoothWithAreas)
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index 7fbfe48f4e..18674cdb17 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -1,12 +1,22 @@
+/**
+ * The base type for nearly all physical objects in SS13
+
+ * Lots and lots of functionality lives here, although in general we are striving to move
+ * as much as possible to the components/elements system
+ */
/atom
layer = TURF_LAYER
plane = GAME_PLANE
- var/level = 2
- var/article // If non-null, overrides a/an/some in all cases
+ appearance_flags = TILE_BOUND
+ var/level = 2
+ ///If non-null, overrides a/an/some in all cases
+ var/article
+
+ ///First atom flags var
var/flags_1 = NONE
+ ///Intearaction flags
var/interaction_flags_atom = NONE
- var/datum/reagents/reagents = null
var/flags_ricochet = NONE
@@ -15,35 +25,52 @@
///When a projectile ricochets off this atom, it deals the normal damage * this modifier to this atom
var/ricochet_damage_mod = 0.33
- //This atom's HUD (med/sec, etc) images. Associative list.
+ ///Reagents holder
+ var/datum/reagents/reagents = null
+
+ ///This atom's HUD (med/sec, etc) images. Associative list.
var/list/image/hud_list = null
- //HUD images that this atom can provide.
+ ///HUD images that this atom can provide.
var/list/hud_possible
- //Value used to increment ex_act() if reactionary_explosions is on
+ ///Value used to increment ex_act() if reactionary_explosions is on
var/explosion_block = 0
- var/list/atom_colours //used to store the different colors on an atom
- //its inherent color, the colored paint applied on it, special color effect etc...
+ /**
+ * used to store the different colors on an atom
+ *
+ * its inherent color, the colored paint applied on it, special color effect etc...
+ */
+ var/list/atom_colours
- var/list/remove_overlays // a very temporary list of overlays to remove
- var/list/add_overlays // a very temporary list of overlays to add
- var/list/managed_vis_overlays //vis overlays managed by SSvis_overlays to automaticaly turn them like other overlays
- ///overlays managed by update_overlays() to prevent removing overlays that weren't added by the same proc
+ /// a very temporary list of overlays to remove
+ var/list/remove_overlays
+ /// a very temporary list of overlays to add
+ var/list/add_overlays
+
+ ///vis overlays managed by SSvis_overlays to automaticaly turn them like other overlays
+ var/list/managed_vis_overlays
+ ///overlays managed by [update_overlays][/atom/proc/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
+ ///Last fingerprints to touch this atom
var/fingerprintslast
var/list/filter_data //For handling persistent filters
+ ///Price of an item in a vending machine, overriding the base vending machine price. Define in terms of paycheck defines as opposed to raw numbers.
var/custom_price
+ ///Price of an item in a vending machine, overriding the premium vending machine price. Define in terms of paycheck defines as opposed to raw numbers.
var/custom_premium_price
+ //List of datums orbiting this atom
var/datum/component/orbiter/orbiters
var/rad_flags = NONE // Will move to flags_1 when i can be arsed to
+ /// Radiation insulation types
var/rad_insulation = RAD_NO_INSULATION
///The custom materials this atom is made of, used by a lot of things like furniture, walls, and floors (if I finish the functionality, that is.)
@@ -72,6 +99,19 @@
///Mobs that are currently do_after'ing this atom, to be cleared from on Destroy()
var/list/targeted_by
+ ///Reference to atom being orbited
+ var/atom/orbit_target
+
+/**
+ * Called when an atom is created in byond (built in engine proc)
+ *
+ * Not a lot happens here in SS13 code, as we offload most of the work to the
+ * [Intialization][/atom/proc/Initialize] proc, mostly we run the preloader
+ * if the preloader is being used and then call [InitAtom][/datum/controller/subsystem/atoms/proc/InitAtom] of which the ultimate
+ * result is that the Intialize proc is called.
+ *
+ * We also generate a tag here if the DF_USE_TAG flag is set on the atom
+ */
/atom/New(loc, ...)
//atom creation method that preloads variables at creation
if(GLOB.use_preloader && (src.type == GLOB._preloader.target_path))//in case the instanciated atom is creating other atoms in New()
@@ -87,24 +127,50 @@
//we were deleted
return
-//Called after New if the map is being loaded. mapload = TRUE
-//Called from base of New if the map is not being loaded. mapload = FALSE
-//This base must be called or derivatives must set initialized to TRUE
-//must not sleep
-//Other parameters are passed from New (excluding loc), this does not happen if mapload is TRUE
-//Must return an Initialize hint. Defined in __DEFINES/subsystems.dm
-
-//Note: the following functions don't call the base for optimization and must copypasta:
-// /turf/Initialize
-// /turf/open/space/Initialize
-
+/**
+ * The primary method that objects are setup in SS13 with
+ *
+ * we don't use New as we have better control over when this is called and we can choose
+ * to delay calls or hook other logic in and so forth
+ *
+ * During roundstart map parsing, atoms are queued for intialization in the base atom/New(),
+ * After the map has loaded, then Initalize is called on all atoms one by one. NB: this
+ * is also true for loading map templates as well, so they don't Initalize until all objects
+ * in the map file are parsed and present in the world
+ *
+ * If you're creating an object at any point after SSInit has run then this proc will be
+ * immediately be called from New.
+ *
+ * mapload: This parameter is true if the atom being loaded is either being intialized during
+ * the Atom subsystem intialization, or if the atom is being loaded from the map template.
+ * If the item is being created at runtime any time after the Atom subsystem is intialized then
+ * it's false.
+ *
+ * You must always call the parent of this proc, otherwise failures will occur as the item
+ * will not be seen as initalized (this can lead to all sorts of strange behaviour, like
+ * the item being completely unclickable)
+ *
+ * You must not sleep in this proc, or any subprocs
+ *
+ * Any parameters from new are passed through (excluding loc), naturally if you're loading from a map
+ * there are no other arguments
+ *
+ * Must return an [initialization hint][INITIALIZE_HINT_NORMAL] or a runtime will occur.
+ *
+ * Note: the following functions don't call the base for optimization and must copypasta handling:
+ * * [/turf/proc/Initialize]
+ * * [/turf/open/space/proc/Initialize]
+ */
/atom/proc/Initialize(mapload, ...)
+ // SHOULD_NOT_SLEEP(TRUE)
+ SHOULD_CALL_PARENT(TRUE)
if(flags_1 & INITIALIZED_1)
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)
@@ -126,14 +192,34 @@
return INITIALIZE_HINT_NORMAL
-//called if Initialize returns INITIALIZE_HINT_LATELOAD
+/**
+ * Late Intialization, for code that should run after all atoms have run Intialization
+ *
+ * To have your LateIntialize proc be called, your atoms [Initalization][/atom/proc/Initialize]
+ * proc must return the hint
+ * [INITIALIZE_HINT_LATELOAD] otherwise you will never be called.
+ *
+ * useful for doing things like finding other machines on GLOB.machines because you can guarantee
+ * that all atoms will actually exist in the "WORLD" at this time and that all their Intialization
+ * code has been run
+ */
/atom/proc/LateInitialize()
- return
+ set waitfor = FALSE
-// Put your AddComponent() calls here
+/// Put your [AddComponent] calls here
/atom/proc/ComponentInitialize()
return
+/**
+ * Top level of the destroy chain for most atoms
+ *
+ * Cleans up the following:
+ * * Removes alternate apperances from huds that see them
+ * * qdels the reagent holder from atoms if it exists
+ * * clears the orbiters list
+ * * clears overlays and priority overlays
+ * * clears the light object
+ */
/atom/Destroy()
if(alternate_appearances)
for(var/K in alternate_appearances)
@@ -143,6 +229,8 @@
if(reagents)
qdel(reagents)
+ orbiters = null // The component is attached to us normaly and will be deleted elsewhere
+
LAZYCLEARLIST(overlays)
for(var/i in targeted_by)
@@ -179,6 +267,16 @@
/atom/proc/CanPass(atom/movable/mover, turf/target)
return !density
+/**
+ * Is this atom currently located on centcom
+ *
+ * Specifically, is it on the z level and within the centcom areas
+ *
+ * You can also be in a shuttleshuttle during endgame transit
+ *
+ * Used in gamemode to identify mobs who have escaped and for some other areas of the code
+ * who don't want atoms where they shouldn't be
+ */
/atom/proc/onCentCom()
var/turf/T = get_turf(src)
if(!T)
@@ -209,6 +307,13 @@
if(T in shuttle_area)
return TRUE
+/**
+ * Is the atom in any of the centcom syndicate areas
+ *
+ * Either in the syndie base on centcom, or any of their shuttles
+ *
+ * Also used in gamemode code for win conditions
+ */
/atom/proc/onSyndieBase()
var/turf/T = get_turf(src)
if(!T)
@@ -222,6 +327,23 @@
return FALSE
+/**
+ * Is the atom in an away mission
+ *
+ * Must be in the away mission z-level to return TRUE
+ *
+ * Also used in gamemode code for win conditions
+ */
+/atom/proc/onAwayMission()
+ var/turf/T = get_turf(src)
+ if(!T)
+ return FALSE
+
+ if(is_away_level(T.z))
+ return TRUE
+
+ return FALSE
+
/atom/proc/attack_hulk(mob/living/carbon/human/user, does_attack_animation = FALSE)
SEND_SIGNAL(src, COMSIG_ATOM_HULK_ATTACK, user)
if(does_attack_animation)
@@ -415,7 +537,7 @@
/// Updates the overlays of the atom
/atom/proc/update_overlays()
- SHOULD_CALL_PARENT(1)
+ SHOULD_CALL_PARENT(TRUE)
. = list()
SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_OVERLAYS, .)
@@ -429,7 +551,7 @@
/atom/proc/contents_explosion(severity, target)
return //For handling the effects of explosions on contents that would not normally be effected
-/atom/proc/ex_act(severity, target)
+/atom/proc/ex_act(severity, target, datum/explosion/E)
set waitfor = FALSE
contents_explosion(severity, target)
SEND_SIGNAL(src, COMSIG_ATOM_EX_ACT, severity, target)
@@ -763,6 +885,9 @@
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_RADIATE, "Radiate")
+ 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)
. = ..()
@@ -806,6 +931,9 @@
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_EDIT_FILTERS] && check_rights(R_VAREDIT))
+ var/client/C = usr.client
+ C?.open_filter_editor(src)
/atom/vv_get_header()
. = ..()
@@ -864,7 +992,7 @@
return
/atom/proc/multitool_check_buffer(user, obj/item/I, silent = FALSE)
- if(!istype(I, /obj/item/multitool))
+ if(!I.tool_behaviour == TOOL_MULTITOOL)
if(user && !silent)
to_chat(user, "[I] has no data buffer!")
return FALSE
@@ -1026,7 +1154,6 @@
victim.log_message(message, LOG_ATTACK, color="blue")
-// Filter stuff
/atom/proc/add_filter(name,priority,list/params)
LAZYINITLIST(filter_data)
var/list/p = params.Copy()
@@ -1042,26 +1169,64 @@
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]
+
+ 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()
+
+/obj/item/update_filters()
+ . = ..()
+ for(var/X in actions)
+ var/datum/action/A = X
+ A.UpdateButtonIcon()
/atom/proc/get_filter(name)
if(filter_data && filter_data[name])
return filters[filter_data.Find(name)]
-/atom/proc/remove_filter(name)
- if(filter_data && filter_data[name])
- filter_data -= name
- update_filters()
- return TRUE
+/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)
///Sets the custom materials for an item.
-/atom/proc/set_custom_materials(var/list/materials, multiplier = 1)
+/atom/proc/set_custom_materials(list/materials, multiplier = 1)
if(custom_materials) //Only runs if custom materials existed at first. Should usually be the case but check anyways
for(var/i in custom_materials)
var/datum/material/custom_material = SSmaterials.GetMaterialRef(i)
- custom_material.on_removed(src, material_flags) //Remove the current materials
+ custom_material.on_removed(src, custom_materials[i], material_flags) //Remove the current materials
if(!length(materials))
custom_materials = null
diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm
index 4715c3bed3..a582fa03e4 100644
--- a/code/game/atoms_movable.dm
+++ b/code/game/atoms_movable.dm
@@ -1,5 +1,7 @@
/atom/movable
layer = OBJ_LAYER
+ glide_size = 8
+ SET_APPEARANCE_FLAGS(TILE_BOUND | PIXEL_SCALE)
var/last_move = null
var/last_move_time = 0
var/anchored = FALSE
@@ -28,10 +30,15 @@
var/atom/movable/moving_from_pull //attempt to resume grab after moving instead of before.
var/list/client_mobs_in_contents // This contains all the client mobs within this container
var/list/acted_explosions //for explosion dodging
- glide_size = 8
- appearance_flags = TILE_BOUND|PIXEL_SCALE
var/datum/forced_movement/force_moving = null //handled soley by forced_movement.dm
- var/movement_type = GROUND //Incase you have multiple types, you automatically use the most useful one. IE: Skating on ice, flippers on water, flying over chasm/space, etc.
+
+ /**
+ * In case you have multiple types, you automatically use the most useful one.
+ * IE: Skating on ice, flippers on water, flying over chasm/space, etc.
+ * I reccomend you use the movetype_handler system and not modify this directly, especially for living mobs.
+ */
+ var/movement_type = GROUND
+
var/atom/movable/pulling
var/grab_state = 0
var/throwforce = 0
@@ -59,6 +66,42 @@
em_block = new(src, render_target)
vis_contents += em_block
+
+/atom/movable/Destroy(force)
+ QDEL_NULL(proximity_monitor)
+ QDEL_NULL(language_holder)
+ QDEL_NULL(em_block)
+
+ unbuckle_all_mobs(force = TRUE)
+
+ if(loc)
+ //Restore air flow if we were blocking it (movables with ATMOS_PASS_PROC will need to do this manually if necessary)
+ if(((CanAtmosPass == ATMOS_PASS_DENSITY && density) || CanAtmosPass == ATMOS_PASS_NO) && isturf(loc))
+ CanAtmosPass = ATMOS_PASS_YES
+ air_update_turf(TRUE)
+ loc.handle_atom_del(src)
+
+ // if(opacity)
+ // RemoveElement(/datum/element/light_blocking)
+
+ invisibility = INVISIBILITY_ABSTRACT
+
+ if(pulledby)
+ pulledby.stop_pulling()
+
+ if(orbiting)
+ orbiting.end_orbit(src)
+ orbiting = null
+
+ . = ..()
+
+ for(var/movable_content in contents)
+ qdel(movable_content)
+
+ LAZYCLEARLIST(client_mobs_in_contents)
+
+ moveToNullspace()
+
/atom/movable/proc/update_emissive_block()
if(blocks_emissive != EMISSIVE_BLOCK_GENERIC)
return
@@ -113,40 +156,52 @@
return FALSE
return T.zPassOut(src, direction, destination) && destination.zPassIn(src, direction, T)
-/atom/movable/vv_edit_var(var_name, var_value)
- var/static/list/banned_edits = list("step_x", "step_y", "step_size")
- var/static/list/careful_edits = list("bound_x", "bound_y", "bound_width", "bound_height")
- if(var_name in banned_edits)
+/atom/movable/vv_edit_var(var_name, var_value, massedit)
+ var/static/list/banned_edits = list("step_x" = TRUE, "step_y" = TRUE, "step_size" = TRUE, "bounds" = TRUE)
+ var/static/list/careful_edits = list("bound_x" = TRUE, "bound_y" = TRUE, "bound_width" = TRUE, "bound_height" = TRUE)
+ if(banned_edits[var_name])
return FALSE //PLEASE no.
- if((var_name in careful_edits) && (var_value % world.icon_size) != 0)
+ if((careful_edits[var_name]) && (var_value % world.icon_size) != 0)
return FALSE
+
switch(var_name)
if(NAMEOF(src, x))
var/turf/T = locate(var_value, y, z)
if(T)
- forceMove(T)
+ admin_teleport(T, !massedit)
return TRUE
return FALSE
if(NAMEOF(src, y))
var/turf/T = locate(x, var_value, z)
if(T)
- forceMove(T)
+ admin_teleport(T, !massedit)
return TRUE
return FALSE
if(NAMEOF(src, z))
var/turf/T = locate(x, y, var_value)
if(T)
- forceMove(T)
+ admin_teleport(T, !massedit)
return TRUE
return FALSE
if(NAMEOF(src, loc))
- if(istype(var_value, /atom))
- forceMove(var_value)
- return TRUE
- else if(isnull(var_value))
- moveToNullspace()
+ if(isatom(var_value) || isnull(var_value))
+ admin_teleport(var_value, !massedit)
return TRUE
return FALSE
+ if(NAMEOF(src, anchored))
+ set_anchored(var_value)
+ . = TRUE
+ if(NAMEOF(src, pulledby))
+ set_pulledby(var_value)
+ . = TRUE
+ if(NAMEOF(src, glide_size))
+ set_glide_size(var_value)
+ . = TRUE
+
+ if(!isnull(.))
+ datum_flags |= DF_VAR_EDITED
+ return
+
return ..()
/atom/movable/proc/start_pulling(atom/movable/AM, state, force = move_force, supress_message = FALSE)
@@ -168,48 +223,73 @@
AMob.grabbedby(src)
return TRUE
stop_pulling()
+
+ // SEND_SIGNAL(src, COMSIG_ATOM_START_PULL, AM, state, force)
+
if(AM.pulledby)
log_combat(AM, AM.pulledby, "pulled from", src)
AM.pulledby.stop_pulling() //an object can't be pulled by two mobs at once.
pulling = AM
- AM.pulledby = src
+ AM.set_pulledby(src)
setGrabState(state)
if(ismob(AM))
var/mob/M = AM
log_combat(src, M, "grabbed", addition="passive grab")
if(!supress_message)
- visible_message("[src] has grabbed [M] passively!")
+ M.visible_message("[src] grabs [M] passively.", \
+ "[src] grabs you passively.")
return TRUE
/atom/movable/proc/stop_pulling()
if(!pulling)
return
- pulling.pulledby = null
+ pulling.set_pulledby(null)
var/mob/living/ex_pulled = pulling
+ setGrabState(GRAB_PASSIVE)
pulling = null
- setGrabState(0)
if(isliving(ex_pulled))
var/mob/living/L = ex_pulled
L.update_mobility()// mob gets up if it was lyng down in a chokehold
+///Reports the event of the change in value of the pulledby variable.
+/atom/movable/proc/set_pulledby(new_pulledby)
+ if(new_pulledby == pulledby)
+ return FALSE //null signals there was a change, be sure to return FALSE if none happened here.
+ . = pulledby
+ pulledby = new_pulledby
+
/atom/movable/proc/Move_Pulled(atom/A)
if(!pulling)
- return
+ return FALSE
if(pulling.anchored || pulling.move_resist > move_force || !pulling.Adjacent(src))
stop_pulling()
- return
+ return FALSE
if(isliving(pulling))
var/mob/living/L = pulling
if(L.buckled && L.buckled.buckle_prevents_pull) //if they're buckled to something that disallows pulling, prevent it
stop_pulling()
- return
+ return FALSE
if(A == loc && pulling.density)
- return
- if(!Process_Spacemove(get_dir(pulling.loc, A)))
- return
- step(pulling, get_dir(pulling.loc, A))
+ return FALSE
+ var/move_dir = get_dir(pulling.loc, A)
+ if(!Process_Spacemove(move_dir))
+ return FALSE
+ pulling.Move(get_step(pulling.loc, move_dir), move_dir, glide_size)
return TRUE
+/**
+ * Recursively set glide size for atom's pulled things
+ */
+/atom/movable/proc/recursive_pulled_glidesize_update()
+ var/list/ran = list()
+ var/atom/movable/updating = pulling
+ while(updating)
+ if(ran[updating])
+ return
+ updating.set_glide_size(glide_size, FALSE)
+ ran[updating] = TRUE
+ updating = updating.pulling
+
/atom/movable/proc/check_pulling()
if(pulling)
var/atom/movable/pullee = pulling
@@ -229,54 +309,57 @@
if(pulledby && moving_diagonally != FIRST_DIAG_STEP && get_dist(src, pulledby) > 1) //separated from our puller and not in the middle of a diagonal move.
pulledby.stop_pulling()
-/atom/movable/Destroy(force)
- QDEL_NULL(proximity_monitor)
- QDEL_NULL(language_holder)
- QDEL_NULL(em_block)
+/atom/movable/proc/set_glide_size(target = 8, recursive = TRUE)
+#ifdef SMOOTH_MOVEMENT
+ // SEND_SIGNAL(src, COMSIG_MOVABLE_UPDATE_GLIDE_SIZE, target)
+ glide_size = target
- unbuckle_all_mobs(force=1)
+ for(var/m in buckled_mobs)
+ var/mob/buckled_mob = m
+ buckled_mob.set_glide_size(target)
- . = ..()
+ if(recursive)
+ recursive_pulled_glidesize_update()
+#else
+ return
+#endif
- if(loc)
- //Restore air flow if we were blocking it (movables with ATMOS_PASS_PROC will need to do this manually if necessary)
- if(((CanAtmosPass == ATMOS_PASS_DENSITY && density) || CanAtmosPass == ATMOS_PASS_NO) && isturf(loc))
- CanAtmosPass = ATMOS_PASS_YES
- air_update_turf(TRUE)
- loc.handle_atom_del(src)
- for(var/atom/movable/AM in contents)
- qdel(AM)
- moveToNullspace()
- invisibility = INVISIBILITY_ABSTRACT
- if(pulledby)
- pulledby.stop_pulling()
-
- if(orbiting)
- orbiting.end_orbit(src)
- orbiting = null
+///Sets the anchored var and returns if it was sucessfully changed or not.
+/atom/movable/proc/set_anchored(anchorvalue)
+ SHOULD_CALL_PARENT(TRUE)
+ if(anchored == anchorvalue)
+ return
+ . = anchored
+ anchored = anchorvalue
+ // SEND_SIGNAL(src, COMSIG_MOVABLE_SET_ANCHORED, anchorvalue)
/atom/movable/proc/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
- set waitfor = 0
+ set waitfor = FALSE
var/hitpush = TRUE
var/impact_signal = SEND_SIGNAL(src, COMSIG_MOVABLE_IMPACT, hit_atom, throwingdatum)
if(impact_signal & COMPONENT_MOVABLE_IMPACT_FLIP_HITPUSH)
hitpush = FALSE // hacky, tie this to something else or a proper workaround later
- if(impact_signal & ~COMPONENT_MOVABLE_IMPACT_NEVERMIND) // in case a signal interceptor broke or deleted the thing before we could process our hit
- return hit_atom.hitby(src, throwingdatum = throwingdatum, hitpush = hitpush)
+ if(!(impact_signal && (impact_signal & COMPONENT_MOVABLE_IMPACT_NEVERMIND))) // in case a signal interceptor broke or deleted the thing before we could process our hit
+ return hit_atom.hitby(src, throwingdatum=throwingdatum, hitpush=hitpush)
/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)
..()
-/atom/movable/proc/safe_throw_at(atom/target, range, speed, mob/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force = INFINITY, messy_throw = TRUE)
+/atom/movable/proc/safe_throw_at(atom/target, range, speed, mob/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force = MOVE_FORCE_STRONG, gentle = FALSE)
if((force < (move_resist * MOVE_FORCE_THROW_RATIO)) || (move_resist == INFINITY))
return
- return throw_at(target, range, speed, thrower, spin, diagonals_first, callback, force, messy_throw)
+ return throw_at(target, range, speed, thrower, spin, diagonals_first, callback, force, gentle)
-/atom/movable/proc/throw_at(atom/target, range, speed, mob/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force = INFINITY, messy_throw = TRUE) //If this returns FALSE then callback will not be called.
+///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, gentle = FALSE, quickstart = TRUE)
. = FALSE
+
+ if(QDELETED(src))
+ CRASH("Qdeleted thing being thrown around.")
+
if (!target || speed <= 0)
return
@@ -288,7 +371,7 @@
//They are moving! Wouldn't it be cool if we calculated their momentum and added it to the throw?
if (thrower && thrower.last_move && thrower.client && thrower.client.move_delay >= world.time + world.tick_lag*2)
- var/user_momentum = thrower.movement_delay()
+ var/user_momentum = thrower.movement_delay() //cached_multiplicative_slowdown
if (!user_momentum) //no movement_delay, this means they move once per byond tick, lets calculate from that instead.
user_momentum = world.tick_lag
@@ -312,19 +395,13 @@
. = 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))
+ thrower = null //Let's not pass a qdeleting reference if any.
+ else
+ target_zone = thrower.zone_selected
+
+ var/datum/thrownthing/TT = new(src, target, get_turf(target), get_dir(src, target), range, speed, thrower, diagonals_first, force, gentle, callback, target_zone)
var/dist_x = abs(target.x - src.x)
var/dist_y = abs(target.y - src.y)
@@ -359,7 +436,8 @@
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/force_pushed(atom/movable/pusher, force = MOVE_FORCE_DEFAULT, direction)
return FALSE
@@ -382,13 +460,13 @@
return TRUE
return ..()
-// called when this atom is removed from a storage item, which is passed on as S. The loc variable is already set to the new destination before this is called.
-/atom/movable/proc/on_exit_storage(datum/component/storage/concrete/S)
- return
+/// called when this atom is removed from a storage item, which is passed on as S. The loc variable is already set to the new destination before this is called.
+/atom/movable/proc/on_exit_storage(datum/component/storage/concrete/S) // rename S to master_storage
+ // SEND_SIGNAL(src, COMSIG_STORAGE_EXITED, master_storage)
-// called when this atom is added into a storage item, which is passed on as S. The loc variable is already set to the storage item.
+/// called when this atom is added into a storage item, which is passed on as S. The loc variable is already set to the storage item.
/atom/movable/proc/on_enter_storage(datum/component/storage/concrete/S)
- return
+ // SEND_SIGNAL(src, COMSIG_STORAGE_ENTERED, master_storage)
/atom/movable/proc/get_spacemove_backup()
var/atom/movable/dense_object_backup
@@ -422,24 +500,26 @@
return //don't do an animation if attacking self
var/pixel_x_diff = 0
var/pixel_y_diff = 0
+ var/turn_dir = 1
var/direction = get_dir(src, A)
if(direction & NORTH)
pixel_y_diff = 8
+ turn_dir = prob(50) ? -1 : 1
else if(direction & SOUTH)
pixel_y_diff = -8
+ turn_dir = prob(50) ? -1 : 1
if(direction & EAST)
pixel_x_diff = 8
else if(direction & WEST)
pixel_x_diff = -8
+ turn_dir = -1
- var/matrix/OM = matrix(transform)
- var/matrix/M = matrix(transform)
- M.Turn(pixel_x_diff ? pixel_x_diff*2 : pick(-16, 16))
-
- animate(src, pixel_x = pixel_x + pixel_x_diff, pixel_y = pixel_y + pixel_y_diff, transform = M, time = 2)
- animate(src, pixel_x = pixel_x - pixel_x_diff, pixel_y = pixel_y - pixel_y_diff, transform = OM, time = 2)
+ var/matrix/initial_transform = matrix(transform)
+ var/matrix/rotated_transform = transform.Turn(15 * turn_dir)
+ animate(src, pixel_x = pixel_x + pixel_x_diff, pixel_y = pixel_y + pixel_y_diff, transform=rotated_transform, time = 1, easing=BACK_EASING|EASE_IN)
+ animate(pixel_x = pixel_x - pixel_x_diff, pixel_y = pixel_y - pixel_y_diff, transform=initial_transform, time = 2, easing=SINE_EASING)
/atom/movable/proc/do_item_attack_animation(atom/A, visual_effect_icon, obj/item/used_item)
var/image/I
@@ -450,21 +530,21 @@
I.plane = GAME_PLANE
// Scale the icon.
- I.transform *= 0.75
+ I.transform *= 0.4
// The icon should not rotate.
I.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA
// Set the direction of the icon animation.
var/direction = get_dir(src, A)
if(direction & NORTH)
- I.pixel_y = -16
+ I.pixel_y = -12
else if(direction & SOUTH)
- I.pixel_y = 16
+ I.pixel_y = 12
if(direction & EAST)
- I.pixel_x = -16
+ I.pixel_x = -14
else if(direction & WEST)
- I.pixel_x = 16
+ I.pixel_x = 14
if(!direction) // Attacked self?!
I.pixel_z = 16
@@ -472,10 +552,12 @@
if(!I)
return
- flick_overlay(I, GLOB.clients, 5) // 5 ticks/half a second
+ flick_overlay(I, GLOB.clients, 10)
// And animate the attack!
- animate(I, alpha = 175, pixel_x = 0, pixel_y = 0, pixel_z = 0, time = 3)
+ animate(I, alpha = 175, transform = matrix() * 0.75, pixel_x = 0, pixel_y = 0, pixel_z = 0, time = 3)
+ animate(time = 1)
+ animate(alpha = 0, time = 3, easing = CIRCULAR_EASING|EASE_OUT)
/atom/movable/vv_get_dropdown()
. = ..()
@@ -492,19 +574,17 @@
return TRUE
//TODO: Better floating
-/atom/movable/proc/float(on)
- if(throwing)
+/atom/movable/proc/float(on, throw_override)
+ if(throwing || !throw_override)
return
- if(on && (!(movement_type & FLOATING) || floating_need_update))
- animate(src, pixel_y = pixel_y + 2, time = 10, loop = -1)
- sleep(10)
- animate(src, pixel_y = pixel_y - 2, time = 10, loop = -1)
- if(!(movement_type & FLOATING))
- setMovetype(movement_type | FLOATING)
- else if (!on && movement_type & FLOATING)
+ if(on && !(movement_type & FLOATING))
+ 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)
setMovetype(movement_type & ~FLOATING)
- floating_need_update = FALSE
+ floating_need_update = FALSE // assume it's done
/* Language procs
* Unless you are doing something very specific, these are the ones you want to use.
@@ -611,10 +691,32 @@
return FALSE
return TRUE
-/// Updates the grab state of the movable
-/// This exists to act as a hook for behaviour
+/**
+ * Updates the grab state of the movable
+ *
+ * This exists to act as a hook for behaviour
+ */
/atom/movable/proc/setGrabState(newstate)
+ if(newstate == grab_state)
+ return
+ // SEND_SIGNAL(src, COMSIG_MOVABLE_SET_GRAB_STATE, newstate)
+ . = grab_state
grab_state = newstate
+ // switch(grab_state) // Current state.
+ // if(GRAB_PASSIVE)
+ // REMOVE_TRAIT(pulling, TRAIT_IMMOBILIZED, CHOKEHOLD_TRAIT)
+ // REMOVE_TRAIT(pulling, TRAIT_HANDS_BLOCKED, CHOKEHOLD_TRAIT)
+ // if(. >= GRAB_NECK) // Previous state was a a neck-grab or higher.
+ // REMOVE_TRAIT(pulling, TRAIT_FLOORED, CHOKEHOLD_TRAIT)
+ // if(GRAB_AGGRESSIVE)
+ // if(. >= GRAB_NECK) // Grab got downgraded.
+ // REMOVE_TRAIT(pulling, TRAIT_FLOORED, CHOKEHOLD_TRAIT)
+ // else // Grab got upgraded from a passive one.
+ // ADD_TRAIT(pulling, TRAIT_IMMOBILIZED, CHOKEHOLD_TRAIT)
+ // ADD_TRAIT(pulling, TRAIT_HANDS_BLOCKED, CHOKEHOLD_TRAIT)
+ // if(GRAB_NECK, GRAB_KILL)
+ // if(. <= GRAB_AGGRESSIVE)
+ // ADD_TRAIT(pulling, TRAIT_FLOORED, CHOKEHOLD_TRAIT)
/obj/item/proc/do_pickup_animation(atom/target)
set waitfor = FALSE
@@ -626,31 +728,24 @@
I.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA
var/turf/T = get_turf(src)
var/direction
- var/to_x = 0
- var/to_y = 0
+ var/to_x = initial(target.pixel_x)
+ var/to_y = initial(target.pixel_y)
if(!QDELETED(T) && !QDELETED(target))
direction = get_dir(T, target)
if(direction & NORTH)
- to_y = 32
+ to_y += 32
else if(direction & SOUTH)
- to_y = -32
+ to_y -= 32
if(direction & EAST)
- to_x = 32
+ to_x += 32
else if(direction & WEST)
- to_x = -32
+ to_x -= 32
if(!direction)
- to_y = 16
+ to_y += 16
flick_overlay(I, GLOB.clients, 6)
var/matrix/M = new
M.Turn(pick(-30, 30))
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/set_anchored(anchorvalue) //literally only for plumbing ran
- SHOULD_CALL_PARENT(TRUE)
- if(anchored == anchorvalue)
- return
- . = anchored
- anchored = anchorvalue
diff --git a/code/game/atoms_movement.dm b/code/game/atoms_movement.dm
index db9424d983..4f07ff6f95 100644
--- a/code/game/atoms_movement.dm
+++ b/code/game/atoms_movement.dm
@@ -5,8 +5,7 @@
// Here's where we rewrite how byond handles movement except slightly different
// To be removed on step_ conversion
// All this work to prevent a second bump
-/atom/movable/Move(atom/newloc, direct=0)
- set waitfor = FALSE //n o
+/atom/movable/Move(atom/newloc, direct=0, glide_size_override = 0)
. = FALSE
if(!newloc || newloc == loc)
return
@@ -52,8 +51,7 @@
//
////////////////////////////////////////
-/atom/movable/Move(atom/newloc, direct)
- set waitfor = FALSE //n o
+/atom/movable/Move(atom/newloc, direct, glide_size_override = 0)
var/atom/movable/pullee = pulling
var/turf/T = loc
if(!moving_from_pull)
@@ -61,6 +59,9 @@
if(!loc || !newloc)
return FALSE
var/atom/oldloc = loc
+ //Early override for some cases like diagonal movement
+ if(glide_size_override)
+ set_glide_size(glide_size_override, FALSE)
if(loc != newloc)
if (!(direct & (direct - 1))) //Cardinal move
@@ -120,32 +121,38 @@
return
if(!loc || (loc == oldloc && oldloc != newloc))
- last_move = NONE
+ last_move = 0
return
+ setDir(direct)
if(.)
- last_move = direct
- setDir(direct)
-
- if(has_buckled_mobs() && !handle_buckled_mob_movement(loc,direct)) //movement failed due to buckled mob(s)
- return FALSE
-
- if(pulling && pulling == pullee && pulling != moving_from_pull) //we were pulling a thing and didn't lose it during our move.
- if(pulling.anchored)
- 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)))
- 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
Moved(oldloc, direct)
+ if(. && pulling && pulling == pullee && pulling != moving_from_pull) //we were pulling a thing and didn't lose it during our move.
+ if(pulling.anchored)
+ 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)))
+ pulling.moving_from_pull = src
+ pulling.Move(T, get_dir(pulling, T), glide_size) //the pullee tries to reach our previous position
+ pulling.moving_from_pull = null
+ check_pulling()
-/atom/movable/proc/handle_buckled_mob_movement(newloc,direct)
+
+ //glide_size strangely enough can change mid movement animation and update correctly while the animation is playing
+ //This means that if you don't override it late like this, it will just be set back by the movement update that's called when you move turfs.
+ if(glide_size_override)
+ set_glide_size(glide_size_override, FALSE)
+
+ last_move = direct
+ if(. && has_buckled_mobs() && !handle_buckled_mob_movement(loc, direct, glide_size_override)) //movement failed due to buckled mob(s)
+ return FALSE
+
+/atom/movable/proc/handle_buckled_mob_movement(newloc, direct, glide_size_override)
for(var/m in buckled_mobs)
var/mob/living/buckled_mob = m
- if(!buckled_mob.Move(newloc, direct))
+ if(!buckled_mob.Move(newloc, direct, glide_size_override))
forceMove(buckled_mob.loc)
last_move = buckled_mob.last_move
inertia_dir = last_move
@@ -155,6 +162,7 @@
//Called after a successful Move(). By this point, we've already moved
/atom/movable/proc/Moved(atom/OldLoc, Dir, Forced = FALSE)
+ SHOULD_CALL_PARENT(TRUE)
SEND_SIGNAL(src, COMSIG_MOVABLE_MOVED, OldLoc, Dir, Forced)
if (!inertia_moving)
inertia_next_move = world.time + inertia_move_delay
@@ -174,6 +182,8 @@
//oldloc = old location on atom, inserted when forceMove is called and ONLY when forceMove is called!
/atom/movable/Crossed(atom/movable/AM, oldloc)
+ // SHOULD_CALL_PARENT(TRUE)
+ . = ..()
SEND_SIGNAL(src, COMSIG_MOVABLE_CROSSED, AM)
/atom/movable/Uncross(atom/movable/AM, atom/newloc)
@@ -204,7 +214,11 @@
var/atom/movable/AM = item
AM.onTransitZ(old_z,new_z)
+///Proc to modify the movement_type and hook behavior associated with it changing.
/atom/movable/proc/setMovetype(newval)
+ if(movement_type == newval)
+ return
+ . = movement_type
movement_type = newval
///////////// FORCED MOVEMENT /////////////
@@ -268,37 +282,44 @@
old_area.Exited(src, null)
loc = null
-//Called whenever an object moves and by mobs when they attempt to move themselves through space
-//And when an object or action applies a force on src, see newtonian_move() below
-//Return 0 to have src start/keep drifting in a no-grav area and 1 to stop/not start drifting
-//Mobs should return 1 if they should be able to move of their own volition, see client/Move() in mob_movement.dm
-//movement_dir == 0 when stopping or any dir when trying to move
+/**
+ * Called whenever an object moves and by mobs when they attempt to move themselves through space
+ * And when an object or action applies a force on src, see [newtonian_move][/atom/movable/proc/newtonian_move]
+ *
+ * Return 0 to have src start/keep drifting in a no-grav area and 1 to stop/not start drifting
+ *
+ * Mobs should return 1 if they should be able to move of their own volition, see [/client/proc/Move]
+ *
+ * Arguments:
+ * * movement_dir - 0 when stopping or any dir when trying to move
+ */
/atom/movable/proc/Process_Spacemove(movement_dir = 0)
if(has_gravity(src))
- return 1
+ return TRUE
- if(pulledby)
- return 1
+ if(pulledby && (pulledby.pulledby != src || moving_from_pull))
+ return TRUE
if(throwing)
- return 1
+ return TRUE
if(!isturf(loc))
- return 1
+ return TRUE
if(locate(/obj/structure/lattice) in range(1, get_turf(src))) //Not realistic but makes pushing things in space easier
- return 1
+ return TRUE
- return 0
+ return FALSE
-/atom/movable/proc/newtonian_move(direction) //Only moves the object if it's under no gravity
- if(!loc || Process_Spacemove(0))
+/// Only moves the object if it's under no gravity
+/atom/movable/proc/newtonian_move(direction)
+ if(!isturf(loc) || Process_Spacemove(0))
inertia_dir = 0
- return 0
+ return FALSE
inertia_dir = direction
if(!direction)
- return 1
+ return TRUE
inertia_last_loc = loc
SSspacedrift.processing[src] = src
- return 1
+ return TRUE
diff --git a/code/game/gamemodes/bloodsucker/bloodsucker.dm b/code/game/gamemodes/bloodsucker/bloodsucker.dm
index c54de16e2e..65321f5820 100644
--- a/code/game/gamemodes/bloodsucker/bloodsucker.dm
+++ b/code/game/gamemodes/bloodsucker/bloodsucker.dm
@@ -24,6 +24,7 @@
traitor_name = "Bloodsucker"
antag_flag = ROLE_BLOODSUCKER
false_report_weight = 1
+ chaos = 4
restricted_jobs = list("AI","Cyborg")
protected_jobs = list("Chaplain", "Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster")
required_players = 20
@@ -87,7 +88,7 @@
// Init Sunlight (called from datum_bloodsucker.on_gain(), in case game mode isn't even Bloodsucker
/datum/game_mode/proc/check_start_sunlight()
// Already Sunlight (and not about to cancel)
- if(istype(bloodsucker_sunlight) && !bloodsucker_sunlight.cancel_me)
+ if(istype(bloodsucker_sunlight))
return
bloodsucker_sunlight = new ()
@@ -97,7 +98,6 @@
if(!istype(bloodsucker_sunlight))
return
if(bloodsuckers.len <= 0)
- bloodsucker_sunlight.cancel_me = TRUE
qdel(bloodsucker_sunlight)
bloodsucker_sunlight = null
diff --git a/code/game/gamemodes/brother/traitor_bro.dm b/code/game/gamemodes/brother/traitor_bro.dm
index 718ed2c103..eda6b5f9e2 100644
--- a/code/game/gamemodes/brother/traitor_bro.dm
+++ b/code/game/gamemodes/brother/traitor_bro.dm
@@ -6,6 +6,7 @@
name = "traitor+brothers"
config_tag = "traitorbro"
required_players = 25
+ chaos = 5
restricted_jobs = list("AI", "Cyborg")
protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster")
diff --git a/code/game/gamemodes/changeling/changeling.dm b/code/game/gamemodes/changeling/changeling.dm
index 8029685bf7..20ef83a54c 100644
--- a/code/game/gamemodes/changeling/changeling.dm
+++ b/code/game/gamemodes/changeling/changeling.dm
@@ -10,6 +10,7 @@ GLOBAL_VAR(changeling_team_objective_type) //If this is not null, we hand our th
config_tag = "changeling"
antag_flag = ROLE_CHANGELING
false_report_weight = 10
+ chaos = 5
restricted_jobs = list("AI", "Cyborg")
protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster") //citadel change - adds HoP, CE, CMO, and RD to ling role blacklist
required_players = 15
diff --git a/code/game/gamemodes/changeling/traitor_chan.dm b/code/game/gamemodes/changeling/traitor_chan.dm
index b010b08bc3..88a1cde8ce 100644
--- a/code/game/gamemodes/changeling/traitor_chan.dm
+++ b/code/game/gamemodes/changeling/traitor_chan.dm
@@ -2,6 +2,7 @@
name = "traitor+changeling"
config_tag = "traitorchan"
false_report_weight = 10
+ chaos = 6
traitors_possible = 3 //hard limit on traitors if scaling is turned off
restricted_jobs = list("AI", "Cyborg")
required_players = 25
diff --git a/code/game/gamemodes/clock_cult/clock_cult.dm b/code/game/gamemodes/clock_cult/clock_cult.dm
index 29455afe56..d8ebf6f20c 100644
--- a/code/game/gamemodes/clock_cult/clock_cult.dm
+++ b/code/game/gamemodes/clock_cult/clock_cult.dm
@@ -134,6 +134,7 @@ Credit where due:
config_tag = "clockwork_cult"
antag_flag = ROLE_SERVANT_OF_RATVAR
false_report_weight = 10
+ chaos = 8
required_players = 24 //Fixing this directly for now since apparently config machine for forcing modes broke.
required_enemies = 3
recommended_enemies = 5
diff --git a/code/game/gamemodes/clown_ops/clown_ops.dm b/code/game/gamemodes/clown_ops/clown_ops.dm
index 108c67ad27..659d2de105 100644
--- a/code/game/gamemodes/clown_ops/clown_ops.dm
+++ b/code/game/gamemodes/clown_ops/clown_ops.dm
@@ -1,7 +1,7 @@
/datum/game_mode/nuclear/clown_ops
name = "clown ops"
config_tag = "clownops"
-
+ chaos = 8
announce_span = "danger"
announce_text = "Clown empire forces are approaching the station in an attempt to HONK it!\n\
Operatives: Secure the nuclear authentication disk and use your bananium fission explosive to HONK the station.\n\
diff --git a/code/game/gamemodes/cult/cult.dm b/code/game/gamemodes/cult/cult.dm
index 8ec4123201..ba9fad7a84 100644
--- a/code/game/gamemodes/cult/cult.dm
+++ b/code/game/gamemodes/cult/cult.dm
@@ -38,6 +38,7 @@
config_tag = "cult"
antag_flag = ROLE_CULTIST
false_report_weight = 10
+ chaos = 8
restricted_jobs = list("AI", "Cyborg")
protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster")
required_players = 30
diff --git a/code/game/gamemodes/devil/devil agent/devil_agent.dm b/code/game/gamemodes/devil/devil agent/devil_agent.dm
index 789cff5c8f..ccfd6f1dd5 100644
--- a/code/game/gamemodes/devil/devil agent/devil_agent.dm
+++ b/code/game/gamemodes/devil/devil agent/devil_agent.dm
@@ -1,6 +1,7 @@
/datum/game_mode/devil/devil_agents
name = "Devil Agents"
config_tag = "devil_agents"
+ chaos = 5
required_players = 25
required_enemies = 3
recommended_enemies = 8
diff --git a/code/game/gamemodes/devil/devil_game_mode.dm b/code/game/gamemodes/devil/devil_game_mode.dm
index 0f2e8f7858..9bf7fc0e82 100644
--- a/code/game/gamemodes/devil/devil_game_mode.dm
+++ b/code/game/gamemodes/devil/devil_game_mode.dm
@@ -3,6 +3,7 @@
config_tag = "devil"
antag_flag = ROLE_DEVIL
false_report_weight = 1
+ chaos = 3
protected_jobs = list("Lawyer", "Curator", "Chaplain", "Head of Security", "Captain", "AI")
required_players = 0
required_enemies = 1
diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets.dm b/code/game/gamemodes/dynamic/dynamic_rulesets.dm
index 3e7e504130..f1e48eb31c 100644
--- a/code/game/gamemodes/dynamic/dynamic_rulesets.dm
+++ b/code/game/gamemodes/dynamic/dynamic_rulesets.dm
@@ -106,6 +106,7 @@
for(var/i in 1 to 3)
if(config_tag in saved_dynamic_rules[i])
weight_mult -= (repeated_mode_adjust[i]/100)
+ weight_mult = max(0,weight_mult)
if(config_tag in costs)
cost = costs[config_tag]
if(config_tag in requirementses)
diff --git a/code/game/gamemodes/eldritch_cult/eldritch_cult.dm b/code/game/gamemodes/eldritch_cult/eldritch_cult.dm
index a3e3c54dce..1693163fa2 100644
--- a/code/game/gamemodes/eldritch_cult/eldritch_cult.dm
+++ b/code/game/gamemodes/eldritch_cult/eldritch_cult.dm
@@ -3,6 +3,7 @@
config_tag = "heresy"
antag_flag = ROLE_HERETIC
false_report_weight = 5
+ chaos = 5
restricted_jobs = list("AI", "Cyborg")
protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster") //citadel change - adds HoP, CE, CMO, and RD to heretic role blacklist
required_players = 15
diff --git a/code/game/gamemodes/extended/extended.dm b/code/game/gamemodes/extended/extended.dm
index 976f3e3eca..a6abcbefbd 100644
--- a/code/game/gamemodes/extended/extended.dm
+++ b/code/game/gamemodes/extended/extended.dm
@@ -3,6 +3,7 @@
config_tag = "secret_extended"
false_report_weight = 5
required_players = 0
+ chaos = 0
announce_span = "notice"
announce_text = "Just have fun and enjoy the game!"
diff --git a/code/game/gamemodes/game_mode.dm b/code/game/gamemodes/game_mode.dm
index 3d7eeb7a8a..10c0154412 100644
--- a/code/game/gamemodes/game_mode.dm
+++ b/code/game/gamemodes/game_mode.dm
@@ -17,6 +17,7 @@
var/config_tag = null
var/votable = 1
var/probability = 0
+ var/chaos = 5 // 0-9, used for weighting round-to-round
var/false_report_weight = 0 //How often will this show up incorrectly in a centcom report?
var/station_was_nuked = 0 //see nuclearbomb.dm and malfunction.dm
var/nuke_off_station = 0 //Used for tracking where the nuke hit
@@ -81,30 +82,43 @@
///Everyone should now be on the station and have their normal gear. This is the place to give the special roles extra things
/datum/game_mode/proc/post_setup(report) //Gamemodes can override the intercept report. Passing TRUE as the argument will force a report.
- //finalize_monster_hunters() Disabled for now
if(!report)
report = !CONFIG_GET(flag/no_intercept_report)
addtimer(CALLBACK(GLOBAL_PROC, .proc/display_roundstart_logout_report), ROUNDSTART_LOGOUT_REPORT_TIME)
- if(prob(20)) //CIT CHANGE - adds a 20% chance for the security level to be the opposite of what it normally is
+ if(prob(20)) //cit-change
flipseclevel = TRUE
+
+ // if(CONFIG_GET(flag/reopen_roundstart_suicide_roles))
+ // var/delay = CONFIG_GET(number/reopen_roundstart_suicide_roles_delay)
+ // if(delay)
+ // delay = (delay SECONDS)
+ // else
+ // delay = (4 MINUTES) //default to 4 minutes if the delay isn't defined.
+ // addtimer(CALLBACK(GLOBAL_PROC, .proc/reopen_roundstart_suicide_roles), delay)
+
if(SSdbcore.Connect())
- var/sql
+ var/list/to_set = list()
+ var/arguments = list()
if(SSticker.mode)
- sql += "game_mode = '[SSticker.mode]'"
+ to_set += "game_mode = :game_mode"
+ arguments["game_mode"] = SSticker.mode
if(GLOB.revdata.originmastercommit)
- if(sql)
- sql += ", "
- sql += "commit_hash = '[GLOB.revdata.originmastercommit]'"
- if(sql)
- var/datum/DBQuery/query_round_game_mode = SSdbcore.NewQuery("UPDATE [format_table_name("round")] SET [sql] WHERE id = [GLOB.round_id]")
+ to_set += "commit_hash = :commit_hash"
+ arguments["commit_hash"] = GLOB.revdata.originmastercommit
+ if(to_set.len)
+ arguments["round_id"] = GLOB.round_id
+ var/datum/db_query/query_round_game_mode = SSdbcore.NewQuery(
+ "UPDATE [format_table_name("round")] SET [to_set.Join(", ")] WHERE id = :round_id",
+ arguments
+ )
query_round_game_mode.Execute()
qdel(query_round_game_mode)
if(report)
addtimer(CALLBACK(src, .proc/send_intercept, 0), rand(waittime_l, waittime_h))
generate_station_goals()
gamemode_ready = TRUE
- return 1
+ return TRUE
///Handles late-join antag assignments
@@ -610,3 +624,10 @@
/// Mode specific info for ghost game_info
/datum/game_mode/proc/ghost_info()
return
+
+/datum/game_mode/proc/get_chaos()
+ var/chaos_levels = CONFIG_GET(keyed_list/chaos_level)
+ if(config_tag in chaos_levels)
+ return chaos_levels[config_tag]
+ else
+ return chaos
diff --git a/code/game/gamemodes/gangs/gang_items.dm b/code/game/gamemodes/gangs/gang_items.dm
index 7d6ecd00f6..d1cf006600 100644
--- a/code/game/gamemodes/gangs/gang_items.dm
+++ b/code/game/gamemodes/gangs/gang_items.dm
@@ -133,7 +133,7 @@
icon_state = "knuckles"
w_class = 3
-datum/gang_item/clothing/shades //Addition: Why not have cool shades on a gang member anyways?
+/datum/gang_item/clothing/shades //Addition: Why not have cool shades on a gang member anyways?
name = "Cool Sunglasses"
id = "glasses"
cost = 5
@@ -313,13 +313,13 @@ datum/gang_item/clothing/shades //Addition: Why not have cool shades on a gang m
permeability_coefficient = 0.01
clothing_flags = NOSLIP
-datum/gang_item/equipment/shield
+/datum/gang_item/equipment/shield
name = "Riot Shield"
id = "riot_shield"
cost = 25
item_path = /obj/item/shield/riot
-datum/gang_item/equipment/gangsheild
+/datum/gang_item/equipment/gangsheild
name = "Tower Shield"
id = "metal"
cost = 45 //High block of melee and even higher for bullets
diff --git a/code/game/gamemodes/gangs/gangs.dm b/code/game/gamemodes/gangs/gangs.dm
index 0dc4a520ef..100669c487 100644
--- a/code/game/gamemodes/gangs/gangs.dm
+++ b/code/game/gamemodes/gangs/gangs.dm
@@ -6,6 +6,7 @@ GLOBAL_LIST_EMPTY(gangs)
name = "gang war"
config_tag = "gang"
antag_flag = ROLE_GANG
+ chaos = 9
restricted_jobs = list("AI", "Cyborg")
protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster")
required_players = 15
diff --git a/code/game/gamemodes/meteor/meteor.dm b/code/game/gamemodes/meteor/meteor.dm
index afeebb770b..78fe6d9324 100644
--- a/code/game/gamemodes/meteor/meteor.dm
+++ b/code/game/gamemodes/meteor/meteor.dm
@@ -2,6 +2,7 @@
name = "meteor"
config_tag = "meteor"
false_report_weight = 1
+ chaos = 9
var/meteordelay = 2000
var/nometeors = 0
var/rampupdelta = 5
diff --git a/code/game/gamemodes/meteor/meteors.dm b/code/game/gamemodes/meteor/meteors.dm
index 5cfec2376a..9953b593d9 100644
--- a/code/game/gamemodes/meteor/meteors.dm
+++ b/code/game/gamemodes/meteor/meteors.dm
@@ -1,4 +1,6 @@
#define DEFAULT_METEOR_LIFETIME 1800
+#define MAP_EDGE_PAD 5
+
GLOBAL_VAR_INIT(meteor_wave_delay, 625) //minimum wait between waves in tenths of seconds
//set to at least 100 unless you want evarr ruining every round
@@ -30,7 +32,7 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust)) //for space dust event
var/turf/pickedgoal
var/max_i = 10//number of tries to spawn meteor.
while(!isspaceturf(pickedstart))
- var/startSide = dir || pick(GLOB.cardinals)
+ var/startSide = (dir ? dir : pick(GLOB.cardinals))
var/startZ = pick(SSmapping.levels_by_trait(ZTRAIT_STATION))
pickedstart = spaceDebrisStartLoc(startSide, startZ)
pickedgoal = spaceDebrisFinishLoc(startSide, startZ)
@@ -46,17 +48,17 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust)) //for space dust event
var/startx
switch(startSide)
if(NORTH)
- starty = world.maxy-(TRANSITIONEDGE+2)
- startx = rand((TRANSITIONEDGE+2), world.maxx-(TRANSITIONEDGE+2))
+ starty = world.maxy-(TRANSITIONEDGE + MAP_EDGE_PAD)
+ startx = rand((TRANSITIONEDGE + MAP_EDGE_PAD), world.maxx-(TRANSITIONEDGE + MAP_EDGE_PAD))
if(EAST)
- starty = rand((TRANSITIONEDGE+2),world.maxy-(TRANSITIONEDGE+2))
- startx = world.maxx-(TRANSITIONEDGE+2)
+ starty = rand((TRANSITIONEDGE + MAP_EDGE_PAD),world.maxy-(TRANSITIONEDGE + MAP_EDGE_PAD))
+ startx = world.maxx-(TRANSITIONEDGE + MAP_EDGE_PAD)
if(SOUTH)
- starty = (TRANSITIONEDGE+2)
- startx = rand((TRANSITIONEDGE+2), world.maxx-(TRANSITIONEDGE+2))
+ starty = (TRANSITIONEDGE + MAP_EDGE_PAD)
+ startx = rand((TRANSITIONEDGE + MAP_EDGE_PAD), world.maxx-(TRANSITIONEDGE + MAP_EDGE_PAD))
if(WEST)
- starty = rand((TRANSITIONEDGE+2), world.maxy-(TRANSITIONEDGE+2))
- startx = (TRANSITIONEDGE+2)
+ starty = rand((TRANSITIONEDGE + MAP_EDGE_PAD), world.maxy-(TRANSITIONEDGE + MAP_EDGE_PAD))
+ startx = (TRANSITIONEDGE + MAP_EDGE_PAD)
. = locate(startx, starty, Z)
/proc/spaceDebrisFinishLoc(startSide, Z)
@@ -64,17 +66,17 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust)) //for space dust event
var/endx
switch(startSide)
if(NORTH)
- endy = (TRANSITIONEDGE+1)
- endx = rand((TRANSITIONEDGE+1), world.maxx-(TRANSITIONEDGE+1))
+ endy = (TRANSITIONEDGE + MAP_EDGE_PAD)
+ endx = rand((TRANSITIONEDGE + MAP_EDGE_PAD), world.maxx-(TRANSITIONEDGE + MAP_EDGE_PAD))
if(EAST)
- endy = rand((TRANSITIONEDGE+1), world.maxy-(TRANSITIONEDGE+1))
- endx = (TRANSITIONEDGE+1)
+ endy = rand((TRANSITIONEDGE + MAP_EDGE_PAD), world.maxy-(TRANSITIONEDGE + MAP_EDGE_PAD))
+ endx = (TRANSITIONEDGE + MAP_EDGE_PAD)
if(SOUTH)
- endy = world.maxy-(TRANSITIONEDGE+1)
- endx = rand((TRANSITIONEDGE+1), world.maxx-(TRANSITIONEDGE+1))
+ endy = world.maxy-(TRANSITIONEDGE + MAP_EDGE_PAD)
+ endx = rand((TRANSITIONEDGE + MAP_EDGE_PAD), world.maxx-(TRANSITIONEDGE + MAP_EDGE_PAD))
if(WEST)
- endy = rand((TRANSITIONEDGE+1),world.maxy-(TRANSITIONEDGE+1))
- endx = world.maxx-(TRANSITIONEDGE+1)
+ endy = rand((TRANSITIONEDGE + MAP_EDGE_PAD),world.maxy-(TRANSITIONEDGE + MAP_EDGE_PAD))
+ endx = world.maxx-(TRANSITIONEDGE + MAP_EDGE_PAD)
. = locate(endx, endy, Z)
///////////////////////
@@ -82,7 +84,7 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust)) //for space dust event
//////////////////////
/obj/effect/meteor
- name = "the concept of meteor"
+ name = "\proper the concept of meteor"
desc = "You should probably run instead of gawking at this."
icon = 'icons/obj/meteor.dmi'
icon_state = "small"
@@ -92,7 +94,7 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust)) //for space dust event
var/hitpwr = 2 //Level of ex_act to be called on hit.
var/dest
pass_flags = PASSTABLE
- var/heavy = 0
+ var/heavy = FALSE
var/meteorsound = 'sound/effects/meteorimpact.ogg'
var/z_original
var/threat = 0 // used for determining which meteors are most interesting
@@ -108,12 +110,12 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust)) //for space dust event
. = ..() //process movement...
+ var/turf/T = get_turf(loc)
if(.)//.. if did move, ram the turf we get in
- var/turf/T = get_turf(loc)
ram_turf(T)
- if(prob(10) && !isspaceturf(T) && !istype(T, /turf/closed/mineral) && !istype(T, /turf/open/floor/plating/asteroid))//randomly takes a 'hit' from ramming
- get_hit()
+ if(prob(10) && !isspaceturf(T) && !istype(T, /turf/closed/mineral) && !istype(T, /turf/open/floor/plating/asteroid))//randomly takes a 'hit' from ramming, and ignore spare ruin aseroids
+ get_hit()
/obj/effect/meteor/Destroy()
if (timerid)
@@ -135,24 +137,25 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust)) //for space dust event
/obj/effect/meteor/Bump(atom/A)
if(A)
ram_turf(get_turf(A))
- playsound(src.loc, meteorsound, 40, 1)
- if(!istype(A, /turf/closed/mineral) && !istype(A, /turf/open/floor/plating/asteroid))
+ playsound(src.loc, meteorsound, 40, TRUE)
+ if(!istype(A, /turf/closed/mineral) && !istype(A, /turf/open/floor/plating/asteroid)) // ignore localstation ruins
get_hit()
/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!.")
+ living_thing.ex_act(hitpwr)
//then, ram the turf if it still exists
if(T)
T.ex_act(hitpwr)
-
//process getting 'hit' by colliding with a dense object
//or randomly when ramming turfs
/obj/effect/meteor/proc/get_hit()
@@ -162,13 +165,10 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust)) //for space dust event
meteor_effect()
qdel(src)
-/obj/effect/meteor/ex_act()
- return
-
/obj/effect/meteor/examine(mob/user)
+ . = ..()
if(!(flags_1 & ADMIN_SPAWNED_1) && isliving(user))
- SSmedals.UnlockMedal(MEDAL_METEOR, user.client)
- return ..()
+ user.client.give_award(/datum/award/achievement/misc/meteor_examine, user)
/obj/effect/meteor/attackby(obj/item/I, mob/user, params)
if(I.tool_behaviour == TOOL_MINING)
@@ -232,7 +232,7 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust)) //for space dust event
name = "big meteor"
icon_state = "large"
hits = 6
- heavy = 1
+ heavy = TRUE
dropamt = 4
threat = 10
@@ -245,7 +245,7 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust)) //for space dust event
name = "flaming meteor"
icon_state = "flaming"
hits = 5
- heavy = 1
+ heavy = TRUE
meteorsound = 'sound/effects/bamf.ogg'
meteordrop = list(/obj/item/stack/ore/plasma)
threat = 20
@@ -258,7 +258,7 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust)) //for space dust event
/obj/effect/meteor/irradiated
name = "glowing meteor"
icon_state = "glowing"
- heavy = 1
+ heavy = TRUE
meteordrop = list(/obj/item/stack/ore/uranium)
threat = 15
@@ -275,7 +275,7 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust)) //for space dust event
icon_state = "meateor"
desc = "Just... don't think too hard about where this thing came from."
hits = 2
- heavy = 1
+ heavy = TRUE
meteorsound = 'sound/effects/blobattack.ogg'
meteordrop = list(/obj/item/reagent_containers/food/snacks/meat/slab/human, /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant, /obj/item/organ/heart, /obj/item/organ/lungs, /obj/item/organ/tongue, /obj/item/organ/appendix/)
var/meteorgibs = /obj/effect/gibspawner/generic
@@ -327,7 +327,7 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust)) //for space dust event
desc = "Your life briefly passes before your eyes the moment you lay them on this monstrosity."
hits = 30
hitpwr = 1
- heavy = 1
+ heavy = TRUE
meteorsound = 'sound/effects/bamf.ogg'
meteordrop = list(/obj/item/stack/ore/plasma)
threat = 50
@@ -358,7 +358,7 @@ GLOBAL_LIST_INIT(meteorsSPOOKY, list(/obj/effect/meteor/pumpkin))
icon = 'icons/obj/meteor_spooky.dmi'
icon_state = "pumpkin"
hits = 10
- heavy = 1
+ heavy = TRUE
dropamt = 1
meteordrop = list(/obj/item/clothing/head/hardhat/pumpkinhead, /obj/item/reagent_containers/food/snacks/grown/pumpkin)
threat = 100
@@ -368,3 +368,4 @@ 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
+#undef MAP_EDGE_PAD
diff --git a/code/game/gamemodes/monkey/monkey.dm b/code/game/gamemodes/monkey/monkey.dm
index 76460ffbb8..c91252258a 100644
--- a/code/game/gamemodes/monkey/monkey.dm
+++ b/code/game/gamemodes/monkey/monkey.dm
@@ -11,6 +11,7 @@
required_players = 20
required_enemies = 1
recommended_enemies = 1
+ chaos = 9
restricted_jobs = list("Cyborg", "AI")
diff --git a/code/game/gamemodes/nuclear/nuclear.dm b/code/game/gamemodes/nuclear/nuclear.dm
index 48a298984c..dcf84e84db 100644
--- a/code/game/gamemodes/nuclear/nuclear.dm
+++ b/code/game/gamemodes/nuclear/nuclear.dm
@@ -2,6 +2,7 @@
name = "nuclear emergency"
config_tag = "nuclear"
false_report_weight = 10
+ chaos = 9
required_players = 28 // 30 players - 3 players to be the nuke ops = 25 players remaining
required_enemies = 2
recommended_enemies = 5
diff --git a/code/game/gamemodes/objective.dm b/code/game/gamemodes/objective.dm
index d8617e0b48..0ee07951a9 100644
--- a/code/game/gamemodes/objective.dm
+++ b/code/game/gamemodes/objective.dm
@@ -455,7 +455,7 @@ If not set, defaults to check_completion instead. Set it. It's used by cryo.
var/target_real_name // Has to be stored because the target's real_name can change over the course of the round
var/target_missing_id
-/datum/objective/escape/escape_with_identity/find_target()
+/datum/objective/escape/escape_with_identity/find_target(dupe_search_range, blacklist)
target = ..()
update_explanation_text()
@@ -553,7 +553,7 @@ GLOBAL_LIST_EMPTY(possible_items)
for(var/I in subtypesof(/datum/objective_item/steal))
new I
-/datum/objective/steal/find_target()
+/datum/objective/steal/find_target(dupe_search_range, blacklist)
var/list/datum/mind/owners = get_owners()
var/approved_targets = list()
check_items:
@@ -631,7 +631,7 @@ GLOBAL_LIST_EMPTY(possible_items_special)
for(var/I in subtypesof(/datum/objective_item/special) + subtypesof(/datum/objective_item/stack))
new I
-/datum/objective/steal/special/find_target()
+/datum/objective/steal/special/find_target(dupe_search_range, blacklist)
return set_target(pick(GLOB.possible_items_special))
/datum/objective/steal/exchange
@@ -844,7 +844,7 @@ GLOBAL_LIST_EMPTY(possible_items_special)
name = "destroy AI"
martyr_compatible = 1
-/datum/objective/destroy/find_target()
+/datum/objective/destroy/find_target(dupe_search_range, blacklist)
var/list/possible_targets = active_ais(1)
var/mob/living/silicon/ai/target_ai = pick(possible_targets)
target = target_ai.mind
@@ -1124,7 +1124,7 @@ GLOBAL_LIST_EMPTY(possible_items_special)
/datum/objective/hoard/heirloom
name = "steal heirloom"
-/datum/objective/hoard/heirloom/find_target()
+/datum/objective/hoard/heirloom/find_target(dupe_search_range, blacklist)
set_target(pick(GLOB.family_heirlooms))
GLOBAL_LIST_EMPTY(traitor_contraband)
@@ -1141,7 +1141,7 @@ GLOBAL_LIST_EMPTY(cult_contraband)
if(!GLOB.cult_contraband.len)
GLOB.cult_contraband = list(/obj/item/clockwork/slab,/obj/item/clockwork/component/belligerent_eye,/obj/item/clockwork/component/belligerent_eye/lens_gem,/obj/item/shuttle_curse,/obj/item/cult_shift)
-/datum/objective/hoard/collector/find_target()
+/datum/objective/hoard/collector/find_target(dupe_search_range, blacklist)
var/obj/item/I
var/I_type
if(prob(50))
@@ -1172,7 +1172,7 @@ GLOBAL_LIST_EMPTY(possible_sabotages)
for(var/I in subtypesof(/datum/sabotage_objective))
new I
-/datum/objective/sabotage/find_target()
+/datum/objective/sabotage/find_target(dupe_search_range, blacklist)
var/list/datum/mind/owners = get_owners()
var/approved_targets = list()
check_sabotages:
diff --git a/code/game/gamemodes/objective_sabotage.dm b/code/game/gamemodes/objective_sabotage.dm
index c8f1ef4713..1fbebfdac4 100644
--- a/code/game/gamemodes/objective_sabotage.dm
+++ b/code/game/gamemodes/objective_sabotage.dm
@@ -33,6 +33,7 @@
/datum/sabotage_objective/processing/check_conditions()
return won
+/*
/datum/sabotage_objective/processing/power_sink
name = "Drain at least 100 megajoules of power using a power sink."
sabotage_type = "powersink"
@@ -44,6 +45,7 @@
for(var/s in GLOB.power_sinks)
var/obj/item/powersink/sink = s
won = max(won,sink.power_drained/1e8)
+*/
/obj/item/paper/guides/antag/supermatter_sabotage
info = "Ways to sabotage a supermatter:
\
diff --git a/code/game/gamemodes/overthrow/overthrow.dm b/code/game/gamemodes/overthrow/overthrow.dm
index dca0c1ade1..6a118567df 100644
--- a/code/game/gamemodes/overthrow/overthrow.dm
+++ b/code/game/gamemodes/overthrow/overthrow.dm
@@ -3,6 +3,7 @@
name = "overthrow"
config_tag = "overthrow"
antag_flag = ROLE_OVERTHROW
+ chaos = 5
restricted_jobs = list("AI", "Cyborg")
protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster")
required_players = 20 // the core idea is of a swift, bloodless coup, so it shouldn't be as chaotic as revs.
diff --git a/code/game/gamemodes/revolution/revolution.dm b/code/game/gamemodes/revolution/revolution.dm
index 9eed18d906..419a74d616 100644
--- a/code/game/gamemodes/revolution/revolution.dm
+++ b/code/game/gamemodes/revolution/revolution.dm
@@ -12,6 +12,7 @@
config_tag = "revolution"
antag_flag = ROLE_REV
false_report_weight = 10
+ chaos = 8
restricted_jobs = list("AI", "Cyborg")
protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster")
required_players = 20
diff --git a/code/game/gamemodes/traitor/double_agents.dm b/code/game/gamemodes/traitor/double_agents.dm
index fc669d4855..c64e508cef 100644
--- a/code/game/gamemodes/traitor/double_agents.dm
+++ b/code/game/gamemodes/traitor/double_agents.dm
@@ -10,6 +10,7 @@
required_enemies = 5
recommended_enemies = 8
reroll_friendly = 0
+ chaos = 7
traitor_name = "Nanotrasen Internal Affairs Agent"
antag_flag = ROLE_INTERNAL_AFFAIRS
diff --git a/code/game/gamemodes/traitor/traitor.dm b/code/game/gamemodes/traitor/traitor.dm
index 8b1b18660a..d42fe615cd 100644
--- a/code/game/gamemodes/traitor/traitor.dm
+++ b/code/game/gamemodes/traitor/traitor.dm
@@ -17,6 +17,7 @@
recommended_enemies = 4
reroll_friendly = 1
enemy_minimum_age = 0
+ chaos = 2
announce_span = "danger"
announce_text = "There are Syndicate agents on the station!\n\
diff --git a/code/game/gamemodes/wizard/wizard.dm b/code/game/gamemodes/wizard/wizard.dm
index 23f065318e..d8cb851aad 100644
--- a/code/game/gamemodes/wizard/wizard.dm
+++ b/code/game/gamemodes/wizard/wizard.dm
@@ -12,6 +12,7 @@
recommended_enemies = 1
enemy_minimum_age = 7
round_ends_with_antag_death = 1
+ chaos = 9
announce_span = "danger"
announce_text = "There is a space wizard attacking the station!\n\
Wizard: Accomplish your objectives and cause mayhem on the station.\n\
diff --git a/code/game/machinery/PDApainter.dm b/code/game/machinery/PDApainter.dm
index 6bac63dff0..82209221ce 100644
--- a/code/game/machinery/PDApainter.dm
+++ b/code/game/machinery/PDApainter.dm
@@ -82,7 +82,7 @@
O.add_fingerprint(user)
update_icon()
- else if(istype(O, /obj/item/weldingtool) && user.a_intent != INTENT_HARM)
+ else if(O.tool_behaviour == TOOL_WELDER && user.a_intent != INTENT_HARM)
if(stat & BROKEN)
if(!O.tool_start_check(user, amount=0))
return
diff --git a/code/game/machinery/aug_manipulator.dm b/code/game/machinery/aug_manipulator.dm
index 50b6180d62..e496973d75 100644
--- a/code/game/machinery/aug_manipulator.dm
+++ b/code/game/machinery/aug_manipulator.dm
@@ -8,7 +8,19 @@
max_integrity = 200
var/obj/item/bodypart/storedpart
var/initial_icon_state
- var/static/list/style_list_icons = list("standard" = 'icons/mob/augmentation/augments.dmi', "engineer" = 'icons/mob/augmentation/augments_engineer.dmi', "security" = 'icons/mob/augmentation/augments_security.dmi', "mining" = 'icons/mob/augmentation/augments_mining.dmi')
+ var/static/list/style_list_icons = list("standard" = 'icons/mob/augmentation/augments.dmi',
+ "engineer" = 'icons/mob/augmentation/augments_engineer.dmi',
+ "security" = 'icons/mob/augmentation/augments_security.dmi',
+ "mining" = 'icons/mob/augmentation/augments_mining.dmi',
+ "Talon" = 'icons/mob/augmentation/cosmetic_prosthetic/talon.dmi',
+ "Nanotrasen" = 'icons/mob/augmentation/cosmetic_prosthetic/nanotrasen.dmi',
+ "Hephaesthus" = 'icons/mob/augmentation/cosmetic_prosthetic/hephaestus.dmi',
+ "Bishop" = 'icons/mob/augmentation/cosmetic_prosthetic/bishop.dmi',
+ "Xion" = 'icons/mob/augmentation/cosmetic_prosthetic/xion.dmi',
+ "Grayson" = 'icons/mob/augmentation/cosmetic_prosthetic/grayson.dmi',
+ "Cybersolutions" = 'icons/mob/augmentation/cosmetic_prosthetic/cybersolutions.dmi',
+ "Ward" = 'icons/mob/augmentation/cosmetic_prosthetic/ward.dmi'
+ )
/obj/machinery/aug_manipulator/examine(mob/user)
. = ..()
@@ -73,7 +85,7 @@
O.add_fingerprint(user)
update_icon()
- else if(istype(O, /obj/item/weldingtool) && user.a_intent != INTENT_HARM)
+ else if(O.tool_behaviour == TOOL_WELDER && user.a_intent != INTENT_HARM)
if(obj_integrity < max_integrity)
if(!O.tool_start_check(user, amount=0))
return
diff --git a/code/game/machinery/autolathe.dm b/code/game/machinery/autolathe.dm
index 1f0687151d..d8a5f7d2c7 100644
--- a/code/game/machinery/autolathe.dm
+++ b/code/game/machinery/autolathe.dm
@@ -1,6 +1,6 @@
-#define AUTOLATHE_MAIN_MENU 1
-#define AUTOLATHE_CATEGORY_MENU 2
-#define AUTOLATHE_SEARCH_MENU 3
+#define AUTOLATHE_MAIN_MENU 1
+#define AUTOLATHE_CATEGORY_MENU 2
+#define AUTOLATHE_SEARCH_MENU 3
/obj/machinery/autolathe
name = "autolathe"
@@ -17,7 +17,7 @@
var/list/L = list()
var/list/LL = list()
var/hacked = FALSE
- var/disabled = 0
+ var/disabled = FALSE
var/shocked = FALSE
var/hack_wire
var/disable_wire
@@ -27,13 +27,13 @@
var/prod_coeff = 1
var/datum/design/being_built
+ var/datum/techweb/stored_research
var/list/datum/design/matching_designs
var/selected_category
var/screen = 1
var/base_price = 25
var/hacked_price = 50
- var/datum/techweb/specialized/autounlocking/stored_research = /datum/techweb/specialized/autounlocking/autolathe
var/list/categories = list(
"Tools",
"Electronics",
@@ -46,19 +46,13 @@
"Dinnerware",
"Imported"
)
- var/list/allowed_materials
-
- /// Base print speed
- var/base_print_speed = 10
/obj/machinery/autolathe/Initialize()
- var/list/mats = allowed_materials
- if(!mats)
- mats = SSmaterials.materialtypes_by_category[MAT_CATEGORY_RIGID]
- AddComponent(/datum/component/material_container, mats, _show_on_examine=TRUE, _after_insert=CALLBACK(src, .proc/AfterMaterialInsert))
+ AddComponent(/datum/component/material_container, SSmaterials.materialtypes_by_category[MAT_CATEGORY_RIGID], 0, TRUE, null, null, CALLBACK(src, .proc/AfterMaterialInsert))
. = ..()
+
wires = new /datum/wires/autolathe(src)
- stored_research = new stored_research
+ stored_research = new /datum/techweb/specialized/autounlocking/autolathe
matching_designs = list()
/obj/machinery/autolathe/Destroy()
@@ -83,7 +77,7 @@
if(AUTOLATHE_SEARCH_MENU)
dat = search_win(user)
- var/datum/browser/popup = new(user, name, name, 400, 500)
+ var/datum/browser/popup = new(user, "autolathe", name, 400, 500)
popup.set_content(dat)
popup.open()
@@ -114,9 +108,9 @@
return TRUE
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.")
+ 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.")
busy = TRUE
var/obj/item/disk/design_disk/D = O
if(do_after(user, 14.4, target = src))
@@ -128,14 +122,16 @@
return ..()
-/obj/machinery/autolathe/proc/AfterMaterialInsert(obj/item/item_inserted, id_inserted, amount_inserted)
+
+/obj/machinery/autolathe/proc/AfterMaterialInsert(item_inserted, id_inserted, amount_inserted)
if(istype(item_inserted, /obj/item/stack/ore/bluespace_crystal))
use_power(MINERAL_MATERIAL_AMOUNT / 10)
- else if(item_inserted.custom_materials?.len && item_inserted.custom_materials[SSmaterials.GetMaterialRef(/datum/material/glass)])
+ else if(custom_materials && custom_materials.len && custom_materials[SSmaterials.GetMaterialRef(/datum/material/glass)])
flick("autolathe_r",src)//plays glass insertion animation by default otherwise
else
flick("autolathe_o",src)//plays metal insertion animation
+
use_power(min(1000, amount_inserted / 100))
updateUsrDialog()
@@ -187,7 +183,7 @@
if(materials.materials[i] > 0)
list_to_show += i
- used_material = input("Choose [used_material]", "Custom Material") as null|anything in list_to_show
+ 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.
custom_materials[used_material] += amount_needed
@@ -198,8 +194,8 @@
busy = TRUE
use_power(power)
icon_state = "autolathe_n"
- var/time = is_stack ? 10 : base_print_speed * coeff * multiplier
- addtimer(CALLBACK(src, .proc/make_item, power, materials_used, custom_materials, multiplier, coeff, is_stack), time)
+ var/time = is_stack ? 32 : (32 * coeff * multiplier) ** 0.8
+ addtimer(CALLBACK(src, .proc/make_item, power, materials_used, custom_materials, multiplier, coeff, is_stack, usr), time)
else
to_chat(usr, "Not enough materials for this operation.")
@@ -218,10 +214,11 @@
return
-/obj/machinery/autolathe/proc/make_item(power, var/list/materials_used, var/list/picked_materials, multiplier, coeff, is_stack)
+/obj/machinery/autolathe/proc/make_item(power, list/materials_used, list/picked_materials, multiplier, coeff, is_stack, mob/user)
var/datum/component/material_container/materials = GetComponent(/datum/component/material_container)
var/atom/A = drop_location()
use_power(power)
+
materials.use_materials(materials_used)
if(is_stack)
@@ -235,6 +232,11 @@
if(length(picked_materials))
new_item.set_custom_materials(picked_materials, 1 / multiplier) //Ensure we get the non multiplied amount
+ for(var/x in picked_materials)
+ var/datum/material/M = x
+ if(!istype(M, /datum/material/glass) && !istype(M, /datum/material/iron))
+ user.client.give_award(/datum/award/achievement/misc/getting_an_upgrade, user)
+
icon_state = "autolathe"
busy = FALSE
@@ -246,12 +248,10 @@
T += MB.rating*75000
var/datum/component/material_container/materials = GetComponent(/datum/component/material_container)
materials.max_amount = T
- var/manips = 0
- var/total_manip_rating = 0
+ T=1.2
for(var/obj/item/stock_parts/manipulator/M in component_parts)
- total_manip_rating += M.rating
- manips++
- prod_coeff = STANDARD_PART_LEVEL_LATHE_COEFFICIENT(total_manip_rating / (manips? manips : 1))
+ 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)
. += ..()
@@ -376,6 +376,7 @@
return materials.has_materials(required_materials)
+
/obj/machinery/autolathe/proc/get_design_cost(datum/design/D)
var/coeff = (ispath(D.build_path, /obj/item/stack) ? 1 : prod_coeff)
var/dat
@@ -416,9 +417,7 @@
hacked = state
for(var/id in SSresearch.techweb_designs)
var/datum/design/D = SSresearch.techweb_design_by_id(id)
- if(D.build_type & stored_research.design_autounlock_skip_types)
- continue
- if((D.build_type & stored_research.design_autounlock_buildtypes) && ("hacked" in D.category))
+ if((D.build_type & AUTOLATHE) && ("hacked" in D.category))
if(hacked)
stored_research.add_design(D)
else
@@ -428,19 +427,24 @@
. = ..()
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
+
/obj/machinery/autolathe/secure
name = "secured autolathe"
desc = "It produces items using metal and glass. This model was reprogrammed without some of the more hazardous designs."
circuit = /obj/item/circuitboard/machine/autolathe/secure
- stored_research = /datum/techweb/specialized/autounlocking/autolathe/public
- base_print_speed = 20
+
+/obj/machinery/autolathe/secure/Initialize()
+ . = ..()
+ stored_research = new /datum/techweb/specialized/autounlocking/autolathe/public
/obj/machinery/autolathe/toy
name = "autoylathe"
desc = "It produces toys using plastic, metal and glass."
circuit = /obj/item/circuitboard/machine/autolathe/toy
-
- stored_research = /datum/techweb/specialized/autounlocking/autolathe/toy
categories = list(
"Toys",
"Figurines",
@@ -453,12 +457,8 @@
"Misc",
"Imported"
)
- allowed_materials = list(
- /datum/material/iron,
- /datum/material/glass,
- /datum/material/plastic
- )
/obj/machinery/autolathe/toy/hacked/Initialize()
. = ..()
adjust_hacked(TRUE)
+ stored_research = new /datum/techweb/specialized/autounlocking/autolathe/toy
diff --git a/code/game/machinery/buttons.dm b/code/game/machinery/buttons.dm
index b5031b68a1..0de76694c0 100644
--- a/code/game/machinery/buttons.dm
+++ b/code/game/machinery/buttons.dm
@@ -65,7 +65,7 @@
. += "button-board"
/obj/machinery/button/attackby(obj/item/W, mob/user, params)
- if(istype(W, /obj/item/screwdriver))
+ if(W.tool_behaviour == TOOL_SCREWDRIVER)
if(panel_open || allowed(user))
default_deconstruction_screwdriver(user, "button-open", "[skin]",W)
update_icon()
@@ -93,7 +93,7 @@
req_access = board.accesses
to_chat(user, "You add [W] to the button.")
- if(!device && !board && istype(W, /obj/item/wrench))
+ if(!device && !board && W.tool_behaviour == TOOL_WRENCH)
to_chat(user, "You start unsecuring the button frame...")
W.play_tool_sound(src)
if(W.use_tool(src, user, 40))
diff --git a/code/game/machinery/camera/camera_assembly.dm b/code/game/machinery/camera/camera_assembly.dm
index b6af0600e4..ba54822140 100644
--- a/code/game/machinery/camera/camera_assembly.dm
+++ b/code/game/machinery/camera/camera_assembly.dm
@@ -39,7 +39,7 @@
switch(state)
if(1)
// State 1
- if(istype(W, /obj/item/weldingtool))
+ if(W.tool_behaviour == TOOL_WELDER)
if(weld(W, user))
to_chat(user, "You weld the assembly securely into place.")
setAnchored(TRUE)
@@ -56,7 +56,7 @@
return
return
- else if(istype(W, /obj/item/weldingtool))
+ else if(W.tool_behaviour == TOOL_WELDER)
if(weld(W, user))
to_chat(user, "You unweld the assembly from its place.")
@@ -133,7 +133,9 @@
qdel(src)
return TRUE
-/obj/structure/camera_assembly/proc/weld(obj/item/weldingtool/W, mob/living/user)
+/obj/structure/camera_assembly/proc/weld(obj/item/W, mob/living/user)
+ if(!W.tool_behaviour == TOOL_WELDER)
+ return
if(!W.tool_start_check(user, amount=0))
return FALSE
to_chat(user, "You start to weld \the [src]...")
diff --git a/code/game/machinery/camera/motion.dm b/code/game/machinery/camera/motion.dm
index e515f56196..af5093d6ca 100644
--- a/code/game/machinery/camera/motion.dm
+++ b/code/game/machinery/camera/motion.dm
@@ -109,4 +109,4 @@
else if (world.time > detectTime)
detectTime = 0
for(var/obj/machinery/computer/security/telescreen/entertainment/TV in GLOB.machines)
- TV.notify(FALSE)
\ No newline at end of file
+ TV.notify(FALSE)
diff --git a/code/game/machinery/cell_charger.dm b/code/game/machinery/cell_charger.dm
index effd70e9ab..25445f0d1c 100644
--- a/code/game/machinery/cell_charger.dm
+++ b/code/game/machinery/cell_charger.dm
@@ -10,7 +10,7 @@
circuit = /obj/item/circuitboard/machine/cell_charger
pass_flags = PASSTABLE
var/obj/item/stock_parts/cell/charging = null
- var/charge_rate = 500
+ var/recharge_coeff = 1
/obj/machinery/cell_charger/update_overlays()
. += ..()
@@ -28,9 +28,10 @@
. = ..()
. += "There's [charging ? "a" : "no"] cell in the charger."
if(charging)
- . += "Current charge: [round(charging.percent(), 1)]%."
+ var/obj/item/stock_parts/cell/C = charging.get_cell()
+ . += "Current charge: [C.percent()]%."
if(in_range(user, src) || isobserver(user))
- . += "The status display reads: Charge rate at [charge_rate]J per cycle."
+ . += "The status display reads: Charge rate at [recharge_coeff*10]J per cycle."
/obj/machinery/cell_charger/attackby(obj/item/W, mob/user, params)
if(istype(W, /obj/item/stock_parts/cell) && !panel_open)
@@ -122,17 +123,18 @@
charging.emp_act(severity)
/obj/machinery/cell_charger/RefreshParts()
- charge_rate = 500
for(var/obj/item/stock_parts/capacitor/C in component_parts)
- charge_rate *= C.rating
+ recharge_coeff = C.rating
/obj/machinery/cell_charger/process()
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
+ if(charging)
+ var/obj/item/stock_parts/cell/C = charging.get_cell()
+ if(C)
+ if(C.charge < C.maxcharge)
+ C.give(C.chargerate * recharge_coeff)
+ use_power(250 * recharge_coeff)
update_icon()
diff --git a/code/game/machinery/cloning.dm b/code/game/machinery/cloning.dm
index 60a310e9d5..e95543cffb 100644
--- a/code/game/machinery/cloning.dm
+++ b/code/game/machinery/cloning.dm
@@ -297,22 +297,20 @@
if(default_deconstruction_crowbar(W))
return
- if(istype(W, /obj/item/multitool))
- var/obj/item/multitool/P = W
-
- if(istype(P.buffer, /obj/machinery/computer/cloning))
- if(get_area(P.buffer) != get_area(src))
+ if(W.tool_behaviour == TOOL_MULTITOOL)
+ if(istype(W.buffer, /obj/machinery/computer/cloning))
+ if(get_area(W.buffer) != get_area(src))
to_chat(user, "-% Cannot link machines across power zones. Buffer cleared %-")
- P.buffer = null
+ W.buffer = null
return
- to_chat(user, "-% Successfully linked [P.buffer] with [src] %-")
- var/obj/machinery/computer/cloning/comp = P.buffer
+ to_chat(user, "-% Successfully linked [W.buffer] with [src] %-")
+ var/obj/machinery/computer/cloning/comp = W.buffer
if(connected)
connected.DetachCloner(src)
comp.AttachCloner(src)
else
- P.buffer = src
- to_chat(user, "-% Successfully stored [REF(P.buffer)] [P.buffer.name] in buffer %-")
+ W.buffer = src
+ to_chat(user, "-% Successfully stored [REF(W.buffer)] [W.buffer] in buffer %-")
return
var/mob/living/mob_occupant = occupant
diff --git a/code/game/machinery/colormate.dm b/code/game/machinery/colormate.dm
index b4306d49a4..d059d492ae 100644
--- a/code/game/machinery/colormate.dm
+++ b/code/game/machinery/colormate.dm
@@ -6,10 +6,12 @@
density = TRUE
anchored = TRUE
circuit = /obj/item/circuitboard/machine/colormate
- var/obj/item/inserted
+ var/atom/movable/inserted
var/activecolor = "#FFFFFF"
var/list/color_matrix_last
var/matrix_mode = FALSE
+ /// Allow holder'd mobs
+ var/allow_mobs = TRUE
/// Minimum lightness for normal mode
var/minimum_normal_lightness = 50
/// Minimum lightness for matrix mode, tested using 4 test colors of full red, green, blue, white.
@@ -42,7 +44,8 @@
icon_state = "colormate"
/obj/machinery/gear_painter/Destroy()
- inserted.forceMove(drop_location())
+ if(inserted) //please i beg you do not drop nulls
+ inserted.forceMove(drop_location())
return ..()
/obj/machinery/gear_painter/attackby(obj/item/I, mob/living/user)
@@ -57,11 +60,22 @@
return
if(user.a_intent == INTENT_HARM)
return ..()
+ if(allow_mobs && istype(I, /obj/item/clothing/head/mob_holder))
+ var/obj/item/clothing/head/mob_holder/H = I
+ var/mob/victim = H.held_mob
+ if(!user.transferItemToLoc(I, src))
+ to_chat(user, "[I] is stuck to your hand!")
+ return
+ if(!QDELETED(H))
+ H.release()
+ insert_mob(victim, user)
if(is_type_in_list(I, allowed_types) && is_operational())
if(!user.transferItemToLoc(I, src))
to_chat(user, "[I] is stuck to your hand!")
return
+ if(QDELETED(I))
+ return
user.visible_message("[user] inserts [I] into [src]'s receptable.")
inserted = I
@@ -69,9 +83,22 @@
else
return ..()
+/obj/machinery/gear_painter/proc/insert_mob(mob/victim, mob/user)
+ if(inserted)
+ return
+ if(user)
+ visible_message("[user] stuffs [victim] into [src]!")
+ inserted = victim
+ inserted.forceMove(src)
+
/obj/machinery/gear_painter/AllowDrop()
return FALSE
+/obj/machinery/gear_painter/handle_atom_del(atom/movable/AM)
+ if(AM == inserted)
+ inserted = null
+ return ..()
+
/obj/machinery/gear_painter/AltClick(mob/user)
. = ..()
if(!user.CanReach(src))
diff --git a/code/game/machinery/computer/aifixer.dm b/code/game/machinery/computer/aifixer.dm
index 8ebb64097d..bd7ae64e16 100644
--- a/code/game/machinery/computer/aifixer.dm
+++ b/code/game/machinery/computer/aifixer.dm
@@ -10,8 +10,8 @@
var/mob/living/silicon/ai/occupier = null
var/active = FALSE
-/obj/machinery/computer/aifixer/attackby(obj/I, mob/user, params)
- if(occupier && istype(I, /obj/item/screwdriver))
+/obj/machinery/computer/aifixer/attackby(obj/item/I, mob/user, params)
+ if(occupier && I.tool_behaviour == TOOL_SCREWDRIVER)
if(stat & (NOPOWER|BROKEN))
to_chat(user, "The screws on [name]'s screen won't budge.")
else
diff --git a/code/game/machinery/computer/apc_control.dm b/code/game/machinery/computer/apc_control.dm
index c14837b8e3..804025961b 100644
--- a/code/game/machinery/computer/apc_control.dm
+++ b/code/game/machinery/computer/apc_control.dm
@@ -151,7 +151,14 @@
var/obj/machinery/power/apc/target = locate(ref) in GLOB.apcs_list
if(!target)
return
- target.vars[type] = target.setsubsystem(text2num(value))
+ value = target.setsubsystem(text2num(value))
+ switch(type) // Sanity check
+ if("equipment", "lighting", "environ")
+ target.vars[type] = value
+ else
+ message_admins("Warning: possible href exploit by [key_name(usr)] - attempted to set [type] on [target] to [value]")
+ log_game("Warning: possible href exploit by [key_name(usr)] - attempted to set [type] on [target] to [value]")
+ return
target.update_icon()
target.update()
var/setTo = ""
diff --git a/code/game/machinery/computer/arcade.dm b/code/game/machinery/computer/arcade.dm
index 077571b931..dd2ebf287f 100644
--- a/code/game/machinery/computer/arcade.dm
+++ b/code/game/machinery/computer/arcade.dm
@@ -3,14 +3,7 @@
#define ARCADE_WEIGHT_RARE 1
#define ARCADE_RATIO_PLUSH 0.20 // average 1 out of 6 wins is a plush.
-/obj/machinery/computer/arcade
- name = "random arcade"
- desc = "random arcade machine"
- icon_state = "arcade"
- icon_keyboard = null
- icon_screen = "invaders"
- clockwork = TRUE //it'd look weird
- var/list/prizes = list(
+GLOBAL_LIST_INIT(arcade_prize_pool, list(
/obj/item/toy/balloon = ARCADE_WEIGHT_USELESS,
/obj/item/toy/beach_ball = ARCADE_WEIGHT_USELESS,
/obj/item/toy/cattoy = ARCADE_WEIGHT_USELESS,
@@ -70,9 +63,16 @@
/obj/item/clothing/mask/fakemoustache/italian = ARCADE_WEIGHT_RARE,
/obj/item/clothing/suit/hooded/wintercoat/ratvar/fake = ARCADE_WEIGHT_TRICK,
/obj/item/clothing/suit/hooded/wintercoat/narsie/fake = ARCADE_WEIGHT_TRICK
- )
+))
+/obj/machinery/computer/arcade
+ name = "random arcade"
+ desc = "random arcade machine"
+ icon_state = "arcade"
+ icon_keyboard = "no_keyboard"
+ icon_screen = "invaders"
light_color = LIGHT_COLOR_GREEN
+ var/list/prize_override
/obj/machinery/computer/arcade/proc/Reset()
return
@@ -91,44 +91,54 @@
var/obj/machinery/computer/arcade/A = new CB.build_path(loc, CB)
A.setDir(dir)
return INITIALIZE_HINT_QDEL
- //The below object acts as a spawner with a wide array of possible picks, most being uninspired references to past/current player characters.
- //Nevertheless, this keeps its ratio constant with the sum of all the others prizes.
- prizes[/obj/item/toy/plush/random] = counterlist_sum(prizes) * ARCADE_RATIO_PLUSH
+
Reset()
-/obj/machinery/computer/arcade/proc/prizevend(mob/user, list/rarity_classes)
- SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "arcade", /datum/mood_event/arcade)
+/obj/machinery/computer/arcade/proc/prizevend(mob/user, prizes = 1)
+ // if(user.mind?.get_skill_level(/datum/skill/gaming) >= SKILL_LEVEL_LEGENDARY && HAS_TRAIT(user, TRAIT_GAMERGOD))
+ // visible_message("[user] inputs an intense cheat code!",
+ // "You hear a flurry of buttons being pressed.")
+ // say("CODE ACTIVATED: EXTRA PRIZES.")
+ // prizes *= 2
+ for(var/i = 0, i < prizes, i++)
+ SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "arcade", /datum/mood_event/arcade)
+ if(prob(0.0001)) //1 in a million
+ new /obj/item/gun/energy/pulse/prize(src)
+ visible_message("[src] dispenses.. woah, a gun! Way past cool.", "You hear a chime and a shot.")
+ user.client.give_award(/datum/award/achievement/misc/pulse, user)
+ return
- if(prob(1) && prob(1) && prob(1)) //Proper 1 in a million
- new /obj/item/gun/energy/pulse/prize(src)
- SSmedals.UnlockMedal(MEDAL_PULSE, usr.client)
+ var/prizeselect
+ if(prize_override)
+ prizeselect = pickweight(prize_override)
+ else
+ prizeselect = pickweight(GLOB.arcade_prize_pool)
+ var/atom/movable/the_prize = new prizeselect(get_turf(src))
+ playsound(src, 'sound/machines/machine_vend.ogg', 50, TRUE, extrarange = -3)
+ visible_message("[src] dispenses [the_prize]!", "You hear a chime and a clunk.")
- if(!contents.len)
- var/list/toy_raffle
- if(rarity_classes)
- for(var/A in prizes)
- if(prizes[A] in rarity_classes)
- LAZYSET(toy_raffle, A, prizes[A])
- if(!toy_raffle)
- toy_raffle = prizes
- var/prizeselect = pickweight(toy_raffle)
- new prizeselect(src)
-
- var/atom/movable/prize = pick(contents)
- visible_message("[src] dispenses [prize]!", "You hear a chime and a clunk.")
-
- prize.forceMove(get_turf(src))
/obj/machinery/computer/arcade/emp_act(severity)
. = ..()
+ var/override = FALSE
+ if(prize_override)
+ override = TRUE
if(stat & (NOPOWER|BROKEN) || . & EMP_PROTECT_SELF)
return
var/empprize = null
- var/num_of_prizes = rand(round(severity/50),round(severity/100))
+ var/num_of_prizes = 0
+ switch(severity)
+ if(1)
+ num_of_prizes = rand(1,4)
+ if(2)
+ num_of_prizes = rand(0,2)
for(var/i = num_of_prizes; i > 0; i--)
- empprize = pickweight(prizes)
+ if(override)
+ empprize = pickweight(prize_override)
+ else
+ empprize = pickweight(GLOB.arcade_prize_pool)
new empprize(loc)
explosion(loc, -1, 0, 1+num_of_prizes, flame_range = 1+num_of_prizes)
diff --git a/code/game/machinery/computer/arcade/battle.dm b/code/game/machinery/computer/arcade/battle.dm
index 5a0f0e9acf..8240a22290 100644
--- a/code/game/machinery/computer/arcade/battle.dm
+++ b/code/game/machinery/computer/arcade/battle.dm
@@ -1,130 +1,399 @@
// ** BATTLE ** //
-
-
/obj/machinery/computer/arcade/battle
name = "arcade machine"
desc = "Does not support Pinball."
icon_state = "arcade"
circuit = /obj/item/circuitboard/computer/arcade/battle
- var/enemy_name = "Space Villain"
- var/temp = "Winners don't use space drugs" //Temporary message, for attack messages, etc
- var/player_hp = 30 //Player health/attack points
- var/player_mp = 10
- var/enemy_hp = 45 //Enemy health/attack points
- var/enemy_mp = 20
- var/gameover = FALSE
- var/blocked = FALSE //Player cannot attack/heal while set
- var/turtle = 0
- var/turn_speed = 5 //Measured in deciseconds.
+ var/enemy_name = "Space Villain"
+ ///Enemy health/attack points
+ var/enemy_hp = 100
+ var/enemy_mp = 40
+ ///Temporary message, for attack messages, etc
+ var/temp = "