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 += "Station Economic Summary:" + ///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 + . += "
The following people made it out as a random hardcore character:" + . += "
" + /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)]
" src << browse(msg.Join(), "window=Player_playtime_check") -/datum/admins/proc/cmd_show_exp_panel(client/C) +/datum/admins/proc/cmd_show_exp_panel(client/client_to_check) if(!check_rights(R_ADMIN)) return - if(!C) - to_chat(usr, "ERROR: Client not found.") + if(!client_to_check) + to_chat(usr, "ERROR: Client not found.", confidential = TRUE) return if(!CONFIG_GET(flag/use_exp_tracking)) - to_chat(usr, "Tracking is disabled in the server configuration file.") + to_chat(usr, "Tracking is disabled in the server configuration file.", confidential = TRUE) return - var/list/body = list() - body += "Playtime for [C.key]
Playtime:" - body += C.get_exp_report() - body += "Toggle Exempt status" - body += "" - usr << browse(body.Join(), "window=playerplaytime[C.ckey];size=550x615") + new /datum/job_report_menu(client_to_check, usr) /datum/admins/proc/toggle_exempt_status(client/C) if(!check_rights(R_ADMIN)) diff --git a/code/modules/admin/secrets.dm b/code/modules/admin/verbs/secrets.dm similarity index 54% rename from code/modules/admin/secrets.dm rename to code/modules/admin/verbs/secrets.dm index ffe5371619..6255198b63 100644 --- a/code/modules/admin/secrets.dm +++ b/code/modules/admin/verbs/secrets.dm @@ -1,132 +1,176 @@ -/datum/admins/proc/Secrets() - if(!check_rights(0)) + + +/client/proc/secrets() //Creates a verb for admins to open up the ui + set name = "Secrets" + set desc = "Abuse harder than you ever have before with this handy dandy semi-misc stuff menu" + set category = "Admin.Game" + SSblackbox.record_feedback("tally", "admin_verb", 1, "Secrets Panel") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + var/datum/secrets_menu/tgui = new(usr)//create the datum + tgui.ui_interact(usr)//datum has a tgui component, here we open the window + +/datum/secrets_menu + var/client/holder //client of whoever is using this datum + var/is_debugger = FALSE + var/is_funmin = FALSE + +/datum/secrets_menu/New(user)//user can either be a client or a mob due to byondcode(tm) + if (istype(user, /client)) + var/client/user_client = user + holder = user_client //if its a client, assign it to holder + else + var/mob/user_mob = user + holder = user_mob.client //if its a mob, assign the mob's client to holder + + is_debugger = check_rights(R_DEBUG) + is_funmin = check_rights(R_FUN) + +/datum/secrets_menu/ui_state(mob/user) + return GLOB.admin_state + +/datum/secrets_menu/ui_close() + qdel(src) + +/datum/secrets_menu/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "Secrets") + ui.open() + +/datum/secrets_menu/ui_data(mob/user) + var/list/data = list() + data["is_debugger"] = is_debugger + data["is_funmin"] = is_funmin + return data + +/datum/secrets_menu/ui_act(action, params) + . = ..() + if(.) + return + if((action != "admin_log" || action != "show_admins" || action != "mentor_log") && !check_rights(R_ADMIN)) return - - var/list/dat = list("The first rule of adminbuse is: you don't talk about the adminbuse.
") - - dat +={" - General Secrets
-
- Admin Log
- Show Admin List
- Mentor Log
-
- "} - - if(check_rights(R_ADMIN,0)) - dat += {" - Admin Secrets
-
- Cure all diseases currently in existence
- Bombing List
- Show current traitors and objectives
- Show last [length(GLOB.lastsignalers)] signalers
- Show last [length(GLOB.lawchanges)] law changes
- Show AI Laws
- Show Game Mode
- Show Crew Manifest
- List DNA (Blood)
- List Fingerprints
- Enable/Disable CTF

- Reset Thunderdome to default state
- Rename Station Name
- Reset Station Name
- Set Night Shift Mode
-
- Shuttles
-
- Move Ferry
- Toggle Arrivals Ferry
- Move Mining Shuttle
- Move Labor Shuttle
-
- "} - - if(check_rights(R_FUN,0)) - dat += {" - Fun Secrets
-
- Trigger a Virus Outbreak
- Turn all humans into monkeys
- Chinese Cartoons
- Change the species of all humans
- Make all areas powered
- Make all areas unpowered
- Power all SMES
- Triple AI mode (needs to be used in the lobby)
- Everyone is the traitor
- AK-47s For Everyone!
- Summon Guns
- Summon Magic
- Summon Events (Toggle)
- There can only be one!
- There can only be one! (40-second delay)
- Make all players stupid
- Egalitarian Station Mode
- Anarcho-Capitalist Station Mode
- Break all lights
- Fix all lights
- The floor is lava! (DANGEROUS: extremely lame)
- Spawn a custom portal storm
-
- Change bomb cap
- Mass Purrbation
- Mass Remove Purrbation
- "} - - dat += "
" - - if(check_rights(R_DEBUG,0)) - dat += {" - Security Level Elevated
-
- Change all maintenance doors to engie/brig access only
- Change all maintenance doors to brig access only
- Remove cap on security officers
-
- "} - - usr << browse(dat.Join(), "window=secrets") - return - - - - - -/datum/admins/proc/Secrets_topic(item,href_list) var/datum/round_event/E - var/ok = 0 - switch(item) + var/ok = FALSE + switch(action) + //Generic Buttons anyone can use. if("admin_log") var/dat = "Admin Log
" for(var/l in GLOB.admin_log) dat += "
  • [l]
  • " if(!GLOB.admin_log.len) dat += "No-one has done anything this round!" - usr << browse(dat, "window=admin_log") - - if("mentor_log") - CitadelMentorLogSecret() - + holder << browse(dat, "window=admin_log") if("show_admins") var/dat = "Current admins:
    " if(GLOB.admin_datums) for(var/ckey in GLOB.admin_datums) var/datum/admins/D = GLOB.admin_datums[ckey] dat += "[ckey] - [D.rank.name]
    " - usr << browse(dat, "window=showadmins;size=600x500") + holder << browse(dat, "window=showadmins;size=600x500") + if("mentor_log") + var/dat = "Mentor Log
    " + for(var/l in GLOB.mentorlog) + dat += "
  • [l]
  • " - if("tdomereset") - if(!check_rights(R_ADMIN)) + if(!GLOB.mentorlog.len) + dat += "No mentors have done anything this round!" + usr << browse(dat, "window=mentor_log") + + //Buttons for debug. + if("maint_access_engiebrig") + if(!is_debugger) return + for(var/obj/machinery/door/airlock/maintenance/M in GLOB.machines) + M.check_access() + if (ACCESS_MAINT_TUNNELS in M.req_access) + M.req_access = list() + M.req_one_access = list(ACCESS_BRIG,ACCESS_ENGINE) + message_admins("[key_name_admin(holder)] made all maint doors engineering and brig access-only.") + if("maint_access_brig") + if(!is_debugger) + return + for(var/obj/machinery/door/airlock/maintenance/M in GLOB.machines) + M.check_access() + if (ACCESS_MAINT_TUNNELS in M.req_access) + M.req_access = list(ACCESS_BRIG) + message_admins("[key_name_admin(holder)] made all maint doors brig access-only.") + if("infinite_sec") + if(!is_debugger) + return + var/datum/job/J = SSjob.GetJob("Security Officer") + if(!J) + return + J.total_positions = -1 + J.spawn_positions = -1 + message_admins("[key_name_admin(holder)] has removed the cap on security officers.") + //Buttons for helpful stuff. This is where people land in the tgui + if("clear_virus") + var/choice = input("Are you sure you want to cure all disease?") in list("Yes", "Cancel") + if(choice == "Yes") + message_admins("[key_name_admin(holder)] has cured all diseases.") + for(var/thing in SSdisease.active_diseases) + var/datum/disease/D = thing + D.cure(0) + if("list_bombers") + var/dat = "Bombing List
    " + for(var/l in GLOB.bombers) + dat += text("[l]
    ") + holder << browse(dat, "window=bombers") + + if("list_signalers") + var/dat = "Showing last [length(GLOB.lastsignalers)] signalers.
    " + for(var/sig in GLOB.lastsignalers) + dat += "[sig]
    " + holder << browse(dat, "window=lastsignalers;size=800x500") + if("list_lawchanges") + var/dat = "Showing last [length(GLOB.lawchanges)] law changes.
    " + for(var/sig in GLOB.lawchanges) + dat += "[sig]
    " + holder << browse(dat, "window=lawchanges;size=800x500") + if("showailaws") + holder.holder.output_ai_laws()//huh, inconvenient var naming, huh? + if("showgm") + if(!SSticker.HasRoundStarted()) + alert("The game hasn't started yet!") + else if (SSticker.mode) + alert("The game mode is [SSticker.mode.name]") + else + alert("For some reason there's a SSticker, but not a game mode") + if("manifest") + var/dat = "Showing Crew Manifest.
    " + dat += "" + for(var/datum/data/record/t in GLOB.data_core.general) + dat += "" + dat += "
    NamePosition
    [t.fields["name"]][t.fields["rank"]]
    " + holder << browse(dat, "window=manifest;size=440x410") + if("dna") + var/dat = "Showing DNA from blood.
    " + dat += "" + for(var/i in GLOB.human_list) + var/mob/living/carbon/human/H = i + if(H.ckey) + dat += "" + dat += "
    NameDNABlood Type
    [H][H.dna.unique_enzymes][H.dna.blood_type]
    " + holder << browse(dat, "window=DNA;size=440x410") + if("fingerprints") + var/dat = "Showing Fingerprints.
    " + dat += "" + for(var/i in GLOB.human_list) + var/mob/living/carbon/human/H = i + if(H.ckey) + dat += "" + dat += "
    NameFingerprints
    [H][md5(H.dna.uni_identity)]
    " + holder << browse(dat, "window=fingerprints;size=440x410") + if("ctfbutton") + toggle_all_ctf(holder) + if("tdomereset") var/delete_mobs = alert("Clear all mobs?","Confirm","Yes","No","Cancel") if(delete_mobs == "Cancel") return - log_admin("[key_name(usr)] reset the thunderdome to default with delete_mobs==[delete_mobs].", 1) - message_admins("[key_name_admin(usr)] reset the thunderdome to default with delete_mobs==[delete_mobs].") + log_admin("[key_name(holder)] reset the thunderdome to default with delete_mobs==[delete_mobs].", 1) + message_admins("[key_name_admin(holder)] reset the thunderdome to default with delete_mobs==[delete_mobs].") - var/area/thunderdome = locate(/area/tdome/arena) + var/area/thunderdome = GLOB.areas_by_type[/area/tdome/arena] if(delete_mobs == "Yes") for(var/mob/living/mob in thunderdome) qdel(mob) //Clear mobs @@ -134,31 +178,24 @@ if(!istype(obj, /obj/machinery/camera) && !istype(obj, /obj/effect/abstract/proximity_checker)) qdel(obj) //Clear objects - var/area/template = locate(/area/tdome/arena_source) + var/area/template = GLOB.areas_by_type[/area/tdome/arena_source] template.copy_contents_to(thunderdome) - - if("clear_virus") - - var/choice = input("Are you sure you want to cure all disease?") in list("Yes", "Cancel") - if(choice == "Yes") - message_admins("[key_name_admin(usr)] has cured all diseases.") - for(var/thing in SSdisease.active_diseases) - var/datum/disease/D = thing - D.cure(0) if("set_name") - if(!check_rights(R_ADMIN)) - return - var/new_name = input(usr, "Please input a new name for the station.", "What?", "") as text|null + var/new_name = input(holder, "Please input a new name for the station.", "What?", "") as text|null if(!new_name) return set_station_name(new_name) - log_admin("[key_name(usr)] renamed the station to \"[new_name]\".") - message_admins("[key_name_admin(usr)] renamed the station to: [new_name].") + log_admin("[key_name(holder)] renamed the station to \"[new_name]\".") + message_admins("[key_name_admin(holder)] renamed the station to: [new_name].") + priority_announce("[command_name()] has renamed the station to \"[new_name]\".") + if("reset_name") + var/new_name = new_station_name() + set_station_name(new_name) + log_admin("[key_name(holder)] reset the station name.") + message_admins("[key_name_admin(holder)] reset the station name.") priority_announce("[command_name()] has renamed the station to \"[new_name]\".") if("night_shift_set") - if(!check_rights(R_ADMIN)) - return - var/val = alert(usr, "What do you want to set night shift to? This will override the automatic system until set to automatic again.", "Night Shift", "On", "Off", "Automatic") + var/val = alert(holder, "What do you want to set night shift to? This will override the automatic system until set to automatic again.", "Night Shift", "On", "Off", "Automatic") switch(val) if("Automatic") if(CONFIG_GET(flag/enable_night_shifts)) @@ -172,321 +209,102 @@ if("Off") SSnightshift.can_fire = FALSE SSnightshift.update_nightshift(FALSE, TRUE) - - if("reset_name") - if(!check_rights(R_ADMIN)) - return - var/new_name = new_station_name() - set_station_name(new_name) - log_admin("[key_name(usr)] reset the station name.") - message_admins("[key_name_admin(usr)] reset the station name.") - priority_announce("[command_name()] has renamed the station to \"[new_name]\".") - - if("list_bombers") - if(!check_rights(R_ADMIN)) - return - var/dat = "Bombing List
    " - for(var/l in GLOB.bombers) - dat += text("[l]
    ") - usr << browse(dat, "window=bombers") - - if("list_signalers") - if(!check_rights(R_ADMIN)) - return - var/dat = "Showing last [length(GLOB.lastsignalers)] signalers.
    " - for(var/sig in GLOB.lastsignalers) - dat += "[sig]
    " - usr << browse(dat, "window=lastsignalers;size=800x500") - - if("list_lawchanges") - if(!check_rights(R_ADMIN)) - return - var/dat = "Showing last [length(GLOB.lawchanges)] law changes.
    " - for(var/sig in GLOB.lawchanges) - dat += "[sig]
    " - usr << browse(dat, "window=lawchanges;size=800x500") - - if("moveminingshuttle") - if(!check_rights(R_ADMIN)) - return - SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Send Mining Shuttle")) - if(!SSshuttle.toggleShuttle("mining","mining_home","mining_away")) - message_admins("[key_name_admin(usr)] moved mining shuttle") - log_admin("[key_name(usr)] moved the mining shuttle") - - if("movelaborshuttle") - if(!check_rights(R_ADMIN)) - return - SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Send Labor Shuttle")) - if(!SSshuttle.toggleShuttle("laborcamp","laborcamp_home","laborcamp_away")) - message_admins("[key_name_admin(usr)] moved labor shuttle") - log_admin("[key_name(usr)] moved the labor shuttle") - if("moveferry") - if(!check_rights(R_ADMIN)) - return SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Send CentCom Ferry")) if(!SSshuttle.toggleShuttle("ferry","ferry_home","ferry_away")) - message_admins("[key_name_admin(usr)] moved the CentCom ferry") - log_admin("[key_name(usr)] moved the CentCom ferry") - + message_admins("[key_name_admin(holder)] moved the CentCom ferry") + log_admin("[key_name(holder)] moved the CentCom ferry") if("togglearrivals") - if(!check_rights(R_ADMIN)) - return var/obj/docking_port/mobile/arrivals/A = SSshuttle.arrivals if(A) var/new_perma = !A.perma_docked A.perma_docked = new_perma SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Permadock Arrivals Shuttle", "[new_perma ? "Enabled" : "Disabled"]")) - message_admins("[key_name_admin(usr)] [new_perma ? "stopped" : "started"] the arrivals shuttle") - log_admin("[key_name(usr)] [new_perma ? "stopped" : "started"] the arrivals shuttle") + message_admins("[key_name_admin(holder)] [new_perma ? "stopped" : "started"] the arrivals shuttle") + log_admin("[key_name(holder)] [new_perma ? "stopped" : "started"] the arrivals shuttle") else - to_chat(usr, "There is no arrivals shuttle") - if("showailaws") - if(!check_rights(R_ADMIN)) - return - output_ai_laws() - if("showgm") - if(!check_rights(R_ADMIN)) - return - if(!SSticker.HasRoundStarted()) - alert("The game hasn't started yet!") - else if (SSticker.mode) - alert("The game mode is [SSticker.mode.name]") - else alert("For some reason there's a SSticker, but not a game mode") - if("manifest") - if(!check_rights(R_ADMIN)) - return - var/dat = "Showing Crew Manifest.
    " - dat += "" - for(var/datum/data/record/t in GLOB.data_core.general) - dat += "" - dat += "
    NamePosition
    [t.fields["name"]][t.fields["rank"]]
    " - usr << browse(dat, "window=manifest;size=440x410") - if("DNA") - if(!check_rights(R_ADMIN)) - return - var/dat = "Showing DNA from blood.
    " - dat += "" - for(var/mob/living/carbon/human/H in GLOB.carbon_list) - if(H.ckey) - dat += "" - dat += "
    NameDNABlood Type
    [H][H.dna.unique_enzymes][H.dna.blood_type]
    " - usr << browse(dat, "window=DNA;size=440x410") - if("fingerprints") - if(!check_rights(R_ADMIN)) - return - var/dat = "Showing Fingerprints.
    " - dat += "" - for(var/mob/living/carbon/human/H in GLOB.carbon_list) - if(H.ckey) - dat += "" - dat += "
    NameFingerprints
    [H][md5(H.dna.uni_identity)]
    " - usr << browse(dat, "window=fingerprints;size=440x410") - - if("monkey") - if(!check_rights(R_FUN)) - return - SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Monkeyize All Humans")) - for(var/mob/living/carbon/human/H in GLOB.carbon_list) - spawn(0) - H.monkeyize() - ok = 1 - - if("allspecies") - if(!check_rights(R_FUN)) - return - var/result = input(usr, "Please choose a new species","Species") as null|anything in GLOB.species_list - if(result) - SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Mass Species Change", "[result]")) - log_admin("[key_name(usr)] turned all humans into [result]", 1) - message_admins("\blue [key_name_admin(usr)] turned all humans into [result]") - var/newtype = GLOB.species_list[result] - for(var/mob/living/carbon/human/H in GLOB.carbon_list) - H.set_species(newtype) - - if("tripleAI") - if(!check_rights(R_FUN)) - return - usr.client.triple_ai() - SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Triple AI")) - - if("power") - if(!check_rights(R_FUN)) - return - SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Power All APCs")) - log_admin("[key_name(usr)] made all areas powered", 1) - message_admins("[key_name_admin(usr)] made all areas powered") - power_restore() - - if("unpower") - if(!check_rights(R_FUN)) - return - SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Depower All APCs")) - log_admin("[key_name(usr)] made all areas unpowered", 1) - message_admins("[key_name_admin(usr)] made all areas unpowered") - power_failure() - - if("quickpower") - if(!check_rights(R_FUN)) - return - SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Power All SMESs")) - log_admin("[key_name(usr)] made all SMESs powered", 1) - message_admins("[key_name_admin(usr)] made all SMESs powered") - power_restore_quick() - - if("traitor_all") - if(!check_rights(R_FUN)) - return - if(!SSticker.HasRoundStarted()) - alert("The game hasn't started yet!") - return - var/objective = stripped_input(usr, "Enter an objective") - if(!objective) - return - SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Traitor All", "[objective]")) - for(var/mob/living/H in GLOB.player_list) - if(!(ishuman(H)||istype(H, /mob/living/silicon/))) - continue - if(H.stat == DEAD || !H.client || !H.mind || ispAI(H)) - continue - if(is_special_character(H)) - continue - var/datum/antagonist/traitor/T = new() - T.give_objectives = FALSE - var/datum/objective/new_objective = new - new_objective.owner = H - new_objective.explanation_text = objective - T.add_objective(new_objective) - H.mind.add_antag_datum(T) - message_admins("[key_name_admin(usr)] used everyone is a traitor secret. Objective is [objective]") - log_admin("[key_name(usr)] used everyone is a traitor secret. Objective is [objective]") - - if("changebombcap") - if(!check_rights(R_FUN)) - return - SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Bomb Cap")) - - var/newBombCap = input(usr,"What would you like the new bomb cap to be. (entered as the light damage range (the 3rd number in common (1,2,3) notation)) Must be above 4)", "New Bomb Cap", GLOB.MAX_EX_LIGHT_RANGE) as num|null - if (!CONFIG_SET(number/bombcap, newBombCap)) - return - - message_admins("[key_name_admin(usr)] changed the bomb cap to [GLOB.MAX_EX_DEVESTATION_RANGE], [GLOB.MAX_EX_HEAVY_RANGE], [GLOB.MAX_EX_LIGHT_RANGE]") - log_admin("[key_name(usr)] changed the bomb cap to [GLOB.MAX_EX_DEVESTATION_RANGE], [GLOB.MAX_EX_HEAVY_RANGE], [GLOB.MAX_EX_LIGHT_RANGE]") - - if("blackout") - if(!check_rights(R_FUN)) - return - SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Break All Lights")) - message_admins("[key_name_admin(usr)] broke all lights") - for(var/obj/machinery/light/L in GLOB.machines) - L.break_light_tube() - - if("anime") - if(!check_rights(R_FUN)) - return - var/animetype = alert("Would you like to have the clothes be changed?",,"Yes","No","Cancel") - - var/droptype - if(animetype =="Yes") - droptype = alert("Make the uniforms Nodrop?",,"Yes","No","Cancel") - - if(animetype == "Cancel" || droptype == "Cancel") - return - SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Chinese Cartoons")) - message_admins("[key_name_admin(usr)] made everything kawaii.") - for(var/mob/living/carbon/human/H in GLOB.carbon_list) - SEND_SOUND(H, sound(get_announcer_sound("animes"))) - - if(H.dna.species.id == "human") - if(H.dna.features["tail_human"] == "None" || H.dna.features["ears"] == "None") - var/obj/item/organ/ears/cat/ears = new - var/obj/item/organ/tail/cat/tail = new - ears.Insert(H, drop_if_replaced=FALSE) - tail.Insert(H, drop_if_replaced=FALSE) - var/list/honorifics = list("[MALE]" = list("kun"), "[FEMALE]" = list("chan","tan"), "[NEUTER]" = list("san"), "[PLURAL]" = list("san")) //John Robust -> Robust-kun - var/list/names = splittext(H.real_name," ") - var/forename = names.len > 1 ? names[2] : names[1] - var/newname = "[forename]-[pick(honorifics["[H.gender]"])]" - H.fully_replace_character_name(H.real_name,newname) - H.update_mutant_bodyparts() - if(animetype == "Yes") - var/seifuku = pick(typesof(/obj/item/clothing/under/costume/schoolgirl)) - var/obj/item/clothing/under/costume/schoolgirl/I = new seifuku - var/olduniform = H.w_uniform - H.temporarilyRemoveItemFromInventory(H.w_uniform, TRUE, FALSE) - H.equip_to_slot_or_del(I, SLOT_W_UNIFORM) - qdel(olduniform) - if(droptype == "Yes") - ADD_TRAIT(I, TRAIT_NODROP, ADMIN_TRAIT) - else - to_chat(H, "You're not kawaii enough for this.") - - if("whiteout") - if(!check_rights(R_FUN)) - return - SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Fix All Lights")) - message_admins("[key_name_admin(usr)] fixed all lights") - for(var/obj/machinery/light/L in GLOB.machines) - L.fix() - - if("floorlava") - SSweather.run_weather(/datum/weather/floor_is_lava) - + to_chat(holder, "There is no arrivals shuttle.", confidential = TRUE) + if("moveminingshuttle") + SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Send Mining Shuttle")) + if(!SSshuttle.toggleShuttle("mining","mining_home","mining_away")) + message_admins("[key_name_admin(usr)] moved mining shuttle") + log_admin("[key_name(usr)] moved the mining shuttle") + if("movelaborshuttle") + SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Send Labor Shuttle")) + if(!SSshuttle.toggleShuttle("laborcamp","laborcamp_home","laborcamp_away")) + message_admins("[key_name_admin(holder)] moved labor shuttle") + log_admin("[key_name(holder)] moved the labor shuttle") + //!fun! buttons. if("virus") - if(!check_rights(R_FUN)) + if(!is_funmin) return SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Virus Outbreak")) switch(alert("Do you want this to be a random disease or do you have something in mind?",,"Make Your Own","Random","Choose")) if("Make Your Own") - AdminCreateVirus(usr.client) + AdminCreateVirus(holder) if("Random") - E = new /datum/round_event/disease_outbreak() + var/datum/round_event_control/disease_outbreak/DC = locate(/datum/round_event_control/disease_outbreak) in SSevents.control + E = DC.runEvent() if("Choose") - var/virus = input("Choose the virus to spread", "BIOHAZARD") as null|anything in typesof(/datum/disease) - E = new /datum/round_event/disease_outbreak{}() - var/datum/round_event/disease_outbreak/DO = E + var/virus = input("Choose the virus to spread", "BIOHAZARD") as null|anything in sortList(typesof(/datum/disease), /proc/cmp_typepaths_asc) + var/datum/round_event_control/disease_outbreak/DC = locate(/datum/round_event_control/disease_outbreak) in SSevents.control + var/datum/round_event/disease_outbreak/DO = DC.runEvent() DO.virus_type = virus - - if("stupify") - if(!check_rights(R_FUN)) + E = DO + if("allspecies") + if(!is_funmin) return - SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Mass Braindamage")) - for(var/mob/living/carbon/human/H in GLOB.player_list) - to_chat(H, "You suddenly feel stupid.") - H.adjustOrganLoss(ORGAN_SLOT_BRAIN, 60, 80) - message_admins("[key_name_admin(usr)] made everybody stupid") - - if("eagles")//SCRAW - if(!check_rights(R_FUN)) + var/result = input(holder, "Please choose a new species","Species") as null|anything in GLOB.species_list + if(result) + SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Mass Species Change", "[result]")) + log_admin("[key_name(holder)] turned all humans into [result]", 1) + message_admins("\blue [key_name_admin(holder)] turned all humans into [result]") + var/newtype = GLOB.species_list[result] + for(var/i in GLOB.human_list) + var/mob/living/carbon/human/H = i + H.set_species(newtype) + if("power") + if(!is_funmin) return - SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Egalitarian Station")) - for(var/obj/machinery/door/airlock/W in GLOB.machines) - if(is_station_level(W.z) && !istype(get_area(W), /area/bridge) && !istype(get_area(W), /area/crew_quarters) && !istype(get_area(W), /area/security/prison)) - W.req_access = list() - message_admins("[key_name_admin(usr)] activated Egalitarian Station mode") - priority_announce("CentCom airlock control override activated. Please take this time to get acquainted with your coworkers.", null, "commandreport") - - if("ak47s") - if(!check_rights(R_FUN)) + SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Power All APCs")) + log_admin("[key_name(holder)] made all areas powered", 1) + message_admins("[key_name_admin(holder)] made all areas powered") + power_restore() + if("unpower") + if(!is_funmin) return - message_admins("[key_name_admin(usr)] activated AK-47s for Everyone!") - usr.client.ak47s() - sound_to_playing_players('sound/misc/ak47s.ogg') - - if("ancap") - if(!check_rights(R_FUN)) + SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Depower All APCs")) + log_admin("[key_name(holder)] made all areas unpowered", 1) + message_admins("[key_name_admin(holder)] made all areas unpowered") + power_failure() + if("quickpower") + if(!is_funmin) return - SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Anarcho-capitalist Station")) - SSeconomy.full_ancap = !SSeconomy.full_ancap - message_admins("[key_name_admin(usr)] toggled Anarcho-capitalist mode") - if(SSeconomy.full_ancap) - priority_announce("The NAP is now in full effect.", null, "commandreport") + SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Power All SMESs")) + log_admin("[key_name(holder)] made all SMESs powered", 1) + message_admins("[key_name_admin(holder)] made all SMESs powered") + power_restore_quick() + // if("anon_name") + // if(!is_funmin) + // return + // holder.anon_names() + // SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Anonymous Names")) + if("tripleAI") + if(!is_funmin) + return + holder.triple_ai() + SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Triple AI")) + if("onlyone") + if(!is_funmin) + return + var/response = alert("Delay by 40 seconds?", "There can, in fact, only be one", "Instant!", "40 seconds (crush the hope of a normal shift)") + if(response == "Instant!") + holder.only_one() else - priority_announce("The NAP has been revoked.", null, "commandreport") - + holder.only_one_delayed() + SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("There Can Be Only One")) if("guns") - if(!check_rights(R_FUN)) + if(!is_funmin) return SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Summon Guns")) var/survivor_probability = 0 @@ -496,23 +314,21 @@ if("All Antags!") survivor_probability = 100 - rightandwrong(SUMMON_GUNS, usr, survivor_probability) - + rightandwrong(SUMMON_GUNS, holder, survivor_probability) if("magic") - if(!check_rights(R_FUN)) + if(!is_funmin) return SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Summon Magic")) var/survivor_probability = 0 - switch(alert("Do you want this to create survivors antagonists?",,"No Antags","Some Antags","All Antags!")) + switch(alert("Do you want this to create magician antagonists?",,"No Antags","Some Antags","All Antags!")) if("Some Antags") survivor_probability = 25 if("All Antags!") survivor_probability = 100 - rightandwrong(SUMMON_MAGIC, usr, survivor_probability) - + rightandwrong(SUMMON_MAGIC, holder, survivor_probability) if("events") - if(!check_rights(R_FUN)) + if(!is_funmin) return if(!SSevents.wizardmode) if(alert("Do you want to toggle summon events on?",,"Yes","No") == "Yes") @@ -528,78 +344,41 @@ SSevents.toggleWizardmode() SSevents.resetFrequency() SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Summon Events", "Disable")) - - if("dorf") - if(!check_rights(R_FUN)) + if("eagles") + if(!is_funmin) return - SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Dwarf Beards")) - for(var/mob/living/carbon/human/B in GLOB.carbon_list) - B.facial_hair_style = "Dward Beard" - B.update_hair() - message_admins("[key_name_admin(usr)] activated dorf mode") - - if("onlyone") - if(!check_rights(R_FUN)) + SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Egalitarian Station")) + for(var/obj/machinery/door/airlock/W in GLOB.machines) + if(is_station_level(W.z) && !istype(get_area(W), /area/bridge) && !istype(get_area(W), /area/crew_quarters) && !istype(get_area(W), /area/security/prison)) + W.req_access = list() + message_admins("[key_name_admin(holder)] activated Egalitarian Station mode") + priority_announce("CentCom airlock control override activated. Please take this time to get acquainted with your coworkers.", null, "commandreport") + if("ancap") + if(!is_funmin) return - SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("There Can Be Only One")) - usr.client.only_one() - sound_to_playing_players('sound/misc/highlander.ogg') - - if("delayed_onlyone") - if(!check_rights(R_FUN)) + SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Anarcho-capitalist Station")) + SSeconomy.full_ancap = !SSeconomy.full_ancap + message_admins("[key_name_admin(holder)] toggled Anarcho-capitalist mode") + if(SSeconomy.full_ancap) + priority_announce("The NAP is now in full effect.", null, "commandreport") + else + priority_announce("The NAP has been revoked.", null, "commandreport") + if("blackout") + if(!is_funmin) return - SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("There Can Be Only One")) - usr.client.only_one_delayed() - sound_to_playing_players('sound/misc/highlander_delayed.ogg') - - if("maint_access_brig") - if(!check_rights(R_DEBUG)) + SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Break All Lights")) + message_admins("[key_name_admin(holder)] broke all lights") + for(var/obj/machinery/light/L in GLOB.machines) + L.break_light_tube() + if("whiteout") + if(!is_funmin) return - for(var/obj/machinery/door/airlock/maintenance/M in GLOB.machines) - M.check_access() - if (ACCESS_MAINT_TUNNELS in M.req_access) - M.req_access = list(ACCESS_BRIG) - message_admins("[key_name_admin(usr)] made all maint doors brig access-only.") - if("maint_access_engiebrig") - if(!check_rights(R_DEBUG)) - return - for(var/obj/machinery/door/airlock/maintenance/M in GLOB.machines) - M.check_access() - if (ACCESS_MAINT_TUNNELS in M.req_access) - M.req_access = list() - M.req_one_access = list(ACCESS_BRIG,ACCESS_ENGINE) - message_admins("[key_name_admin(usr)] made all maint doors engineering and brig access-only.") - if("infinite_sec") - if(!check_rights(R_DEBUG)) - return - var/datum/job/J = SSjob.GetJob("Security Officer") - if(!J) - return - J.total_positions = -1 - J.spawn_positions = -1 - message_admins("[key_name_admin(usr)] has removed the cap on security officers.") - - if("ctfbutton") - if(!check_rights(R_ADMIN)) - return - toggle_all_ctf(usr) - if("masspurrbation") - if(!check_rights(R_FUN)) - return - mass_purrbation() - message_admins("[key_name_admin(usr)] has put everyone on \ - purrbation!") - log_admin("[key_name(usr)] has put everyone on purrbation.") - if("massremovepurrbation") - if(!check_rights(R_FUN)) - return - mass_remove_purrbation() - message_admins("[key_name_admin(usr)] has removed everyone from \ - purrbation.") - log_admin("[key_name(usr)] has removed everyone from purrbation.") - + SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Fix All Lights")) + message_admins("[key_name_admin(holder)] fixed all lights") + for(var/obj/machinery/light/L in GLOB.machines) + L.fix() if("customportal") - if(!check_rights(R_FUN)) + if(!is_funmin) return var/list/settings = list( @@ -620,14 +399,14 @@ ) ) - message_admins("[key_name(usr)] is creating a custom portal storm...") - var/list/prefreturn = presentpreflikepicker(usr,"Customize Portal Storm", "Customize Portal Storm", Button1="Ok", width = 600, StealFocus = 1,Timeout = 0, settings=settings) + message_admins("[key_name(holder)] is creating a custom portal storm...") + var/list/prefreturn = presentpreflikepicker(holder,"Customize Portal Storm", "Customize Portal Storm", Button1="Ok", width = 600, StealFocus = 1,Timeout = 0, settings=settings) if (prefreturn["button"] == 1) var/list/prefs = settings["mainsettings"] if (prefs["amount"]["value"] < 1 || prefs["portalnum"]["value"] < 1) - to_chat(usr, "Number of portals and mobs to spawn must be at least 1") + to_chat(holder, "Number of portals and mobs to spawn must be at least 1.", confidential = TRUE) return var/mob/pathToSpawn = prefs["typepath"]["value"] @@ -635,7 +414,7 @@ pathToSpawn = text2path(pathToSpawn) if (!ispath(pathToSpawn)) - to_chat(usr, "Invalid path [pathToSpawn]") + to_chat(holder, "Invalid path [pathToSpawn].", confidential = TRUE) return var/list/candidates = list() @@ -653,8 +432,8 @@ var/mutable_appearance/storm = mutable_appearance('icons/obj/tesla_engine/energy_ball.dmi', "energy_ball_fast", FLY_LAYER) storm.color = prefs["color"]["value"] - message_admins("[key_name_admin(usr)] has created a customized portal storm that will spawn [prefs["portalnum"]["value"]] portals, each of them spawning [prefs["amount"]["value"]] of [pathToSpawn]") - log_admin("[key_name(usr)] has created a customized portal storm that will spawn [prefs["portalnum"]["value"]] portals, each of them spawning [prefs["amount"]["value"]] of [pathToSpawn]") + message_admins("[key_name_admin(holder)] has created a customized portal storm that will spawn [prefs["portalnum"]["value"]] portals, each of them spawning [prefs["amount"]["value"]] of [pathToSpawn]") + log_admin("[key_name(holder)] has created a customized portal storm that will spawn [prefs["portalnum"]["value"]] portals, each of them spawning [prefs["amount"]["value"]] of [pathToSpawn]") var/outfit = prefs["humanoutfit"]["value"] if (!ispath(outfit)) @@ -668,20 +447,157 @@ addtimer(CALLBACK(GLOBAL_PROC, .proc/doPortalSpawn, get_random_station_turf(), pathToSpawn, length(ghostcandidates), storm, ghostcandidates, outfit), i*prefs["delay"]["value"]) else if (prefs["playersonly"]["value"] != "Yes") addtimer(CALLBACK(GLOBAL_PROC, .proc/doPortalSpawn, get_random_station_turf(), pathToSpawn, prefs["amount"]["value"], storm, null, outfit), i*prefs["delay"]["value"]) + if("changebombcap") + if(!is_funmin) + return + SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Bomb Cap")) + var/newBombCap = input(holder,"What would you like the new bomb cap to be. (entered as the light damage range (the 3rd number in common (1,2,3) notation)) Must be above 4)", "New Bomb Cap", GLOB.MAX_EX_LIGHT_RANGE) as num|null + if (!CONFIG_SET(number/bombcap, newBombCap)) + return + + message_admins("[key_name_admin(holder)] changed the bomb cap to [GLOB.MAX_EX_DEVESTATION_RANGE], [GLOB.MAX_EX_HEAVY_RANGE], [GLOB.MAX_EX_LIGHT_RANGE]") + log_admin("[key_name(holder)] changed the bomb cap to [GLOB.MAX_EX_DEVESTATION_RANGE], [GLOB.MAX_EX_HEAVY_RANGE], [GLOB.MAX_EX_LIGHT_RANGE]") + //buttons that are fun for exactly you and nobody else. + if("monkey") + if(!is_funmin) + return + SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Monkeyize All Humans")) + for(var/i in GLOB.human_list) + var/mob/living/carbon/human/H = i + INVOKE_ASYNC(H, /mob/living/carbon.proc/monkeyize) + ok = TRUE + if("traitor_all") + if(!is_funmin) + return + if(!SSticker.HasRoundStarted()) + alert("The game hasn't started yet!") + return + var/objective = stripped_input(holder, "Enter an objective") + if(!objective) + return + SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Traitor All", "[objective]")) + for(var/mob/living/H in GLOB.player_list) + if(!(ishuman(H)||istype(H, /mob/living/silicon/))) + continue + if(H.stat == DEAD || !H.mind || ispAI(H)) + continue + if(is_special_character(H)) + continue + var/datum/antagonist/traitor/T = new() + T.give_objectives = FALSE + var/datum/objective/new_objective = new + new_objective.owner = H + new_objective.explanation_text = objective + T.add_objective(new_objective) + H.mind.add_antag_datum(T) + message_admins("[key_name_admin(holder)] used everyone is a traitor secret. Objective is [objective]") + log_admin("[key_name(holder)] used everyone is a traitor secret. Objective is [objective]") + if("ak47s") + if(!is_funmin) + return + if(!SSticker.HasRoundStarted()) + alert("The game hasn't started yet!") + return + message_admins("[key_name_admin(holder)] activated AK-47s for Everyone!") + holder.ak47s() + sound_to_playing_players('sound/misc/ak47s.ogg') + + if("massbraindamage") + if(!is_funmin) + return + SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Mass Braindamage")) + for(var/mob/living/carbon/human/H in GLOB.player_list) + to_chat(H, "You suddenly feel stupid.", confidential = TRUE) + H.adjustOrganLoss(ORGAN_SLOT_BRAIN, 60, 80) + message_admins("[key_name_admin(holder)] made everybody brain damaged") + if("floorlava") + SSweather.run_weather(/datum/weather/floor_is_lava) + if("anime") + if(!is_funmin) + return + var/animetype = alert("Would you like to have the clothes be changed?",,"Yes","No","Cancel") + + var/droptype + if(animetype =="Yes") + droptype = alert("Make the uniforms Nodrop?",,"Yes","No","Cancel") + + if(animetype == "Cancel" || droptype == "Cancel") + return + SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Chinese Cartoons")) + message_admins("[key_name_admin(holder)] made everything kawaii.") + for(var/i in GLOB.human_list) + var/mob/living/carbon/human/H = i + SEND_SOUND(H, sound(get_announcer_sound("animes"))) + + if(H.dna.species.id == "human") + if(H.dna.features["tail_human"] == "None" || H.dna.features["ears"] == "None") + var/obj/item/organ/ears/cat/ears = new + var/obj/item/organ/tail/cat/tail = new + ears.Insert(H, drop_if_replaced=FALSE) + tail.Insert(H, drop_if_replaced=FALSE) + var/list/honorifics = list("[MALE]" = list("kun"), "[FEMALE]" = list("chan","tan"), "[NEUTER]" = list("san"), "[PLURAL]" = list("san")) //John Robust -> Robust-kun + var/list/names = splittext(H.real_name," ") + var/forename = names.len > 1 ? names[2] : names[1] + var/newname = "[forename]-[pick(honorifics["[H.gender]"])]" + H.fully_replace_character_name(H.real_name,newname) + H.update_mutant_bodyparts() + if(animetype == "Yes") + var/seifuku = pick(typesof(/obj/item/clothing/under/costume/schoolgirl)) + var/obj/item/clothing/under/costume/schoolgirl/I = new seifuku + var/olduniform = H.w_uniform + H.temporarilyRemoveItemFromInventory(H.w_uniform, TRUE, FALSE) + H.equip_to_slot_or_del(I, ITEM_SLOT_ICLOTHING) + qdel(olduniform) + if(droptype == "Yes") + ADD_TRAIT(I, TRAIT_NODROP, ADMIN_TRAIT) + else + to_chat(H, "You're not kawaii enough for this!", confidential = TRUE) + if("masspurrbation") + if(!is_funmin) + return + mass_purrbation() + message_admins("[key_name_admin(holder)] has put everyone on \ + purrbation!") + log_admin("[key_name(holder)] has put everyone on purrbation.") + if("massremovepurrbation") + if(!is_funmin) + return + mass_remove_purrbation() + message_admins("[key_name_admin(holder)] has removed everyone from \ + purrbation.") + log_admin("[key_name(holder)] has removed everyone from purrbation.") + // if("massimmerse") // my immursion is ruinned :( + // if(!is_funmin) + // return + // mass_immerse() + // message_admins("[key_name_admin(holder)] has Fully Immersed + // everyone!") + // log_admin("[key_name(holder)] has Fully Immersed everyone.") + // if("unmassimmerse") + // if(!is_funmin) + // return + // mass_immerse(remove=TRUE) + // message_admins("[key_name_admin(holder)] has Un-Fully Immersed + // everyone!") + // log_admin("[key_name(holder)] has Un-Fully Immersed everyone.") if(E) E.processing = FALSE if(E.announceWhen>0) - if(alert(usr, "Would you like to alert the crew?", "Alert", "Yes", "No") == "No") - E.announceWhen = -1 + switch(alert(holder, "Would you like to alert the crew?", "Alert", "Yes", "No", "Cancel")) + if("Cancel") + E.kill() + return + if("No") + E.announceWhen = -1 E.processing = TRUE - if (usr) - log_admin("[key_name(usr)] used secret [item]") - if (ok) - to_chat(world, text("A secret has been activated by []!", usr.key)) + if(holder) + log_admin("[key_name(holder)] used secret [action]") + if(ok) + to_chat(world, text("A secret has been activated by []!", holder.key), confidential = TRUE) /proc/portalAnnounce(announcement, playlightning) - set waitfor = 0 + set waitfor = FALSE if (playlightning) sound_to_playing_players('sound/magic/lightning_chargeup.ogg') sleep(80) @@ -704,4 +620,4 @@ H.equipOutfit(humanoutfit) var/turf/T = get_step(loc, SOUTHWEST) flick_overlay_static(portal_appearance, T, 15) - playsound(T, 'sound/magic/lightningbolt.ogg', rand(80, 100), 1) + playsound(T, 'sound/magic/lightningbolt.ogg', rand(80, 100), TRUE) diff --git a/code/modules/admin/view_variables/filterrific.dm b/code/modules/admin/view_variables/filterrific.dm new file mode 100644 index 0000000000..e651028cbe --- /dev/null +++ b/code/modules/admin/view_variables/filterrific.dm @@ -0,0 +1,99 @@ +/datum/filter_editor + var/atom/target + +/datum/filter_editor/New(atom/target) + src.target = target + +/datum/filter_editor/ui_state(mob/user) + return GLOB.admin_state + +/datum/filter_editor/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "Filteriffic") + ui.open() + +/datum/filter_editor/ui_static_data(mob/user) + var/list/data = list() + data["filter_info"] = GLOB.master_filter_info + return data + +/datum/filter_editor/ui_data() + var/list/data = list() + data["target_name"] = target.name + data["target_filter_data"] = target.filter_data + return data + +/datum/filter_editor/ui_act(action, list/params) + . = ..() + if(.) + return + + switch(action) + if("add_filter") + var/target_name = params["name"] + while(target.filter_data && target.filter_data[target_name]) + target_name = "[target_name]-dupe" + target.add_filter(target_name, params["priority"], list("type" = params["type"])) + . = TRUE + if("remove_filter") + target.remove_filter(params["name"]) + . = TRUE + if("rename_filter") + var/list/filter_data = target.filter_data[params["name"]] + target.remove_filter(params["name"]) + target.add_filter(params["new_name"], filter_data["priority"], filter_data) + . = TRUE + if("edit_filter") + target.remove_filter(params["name"]) + target.add_filter(params["name"], params["priority"], params["new_filter"]) + . = TRUE + if("change_priority") + var/new_priority = params["new_priority"] + target.change_filter_priority(params["name"], new_priority) + . = TRUE + if("transition_filter_value") + target.transition_filter(params["name"], 4, params["new_data"]) + . = TRUE + if("modify_filter_value") + var/list/old_filter_data = target.filter_data[params["name"]] + var/list/new_filter_data = old_filter_data.Copy() + for(var/entry in params["new_data"]) + new_filter_data[entry] = params["new_data"][entry] + for(var/entry in new_filter_data) + if(entry == GLOB.master_filter_info[old_filter_data["type"]]["defaults"][entry]) + new_filter_data.Remove(entry) + target.remove_filter(params["name"]) + target.add_filter(params["name"], old_filter_data["priority"], new_filter_data) + . = TRUE + if("modify_color_value") + var/new_color = input(usr, "Pick new filter color", "Filteriffic Colors!") as color|null + if(new_color) + target.transition_filter(params["name"], 4, list("color" = new_color)) + . = TRUE + if("modify_icon_value") + var/icon/new_icon = input("Pick icon:", "Icon") as null|icon + if(new_icon) + target.filter_data[params["name"]]["icon"] = new_icon + target.update_filters() + . = TRUE + if("mass_apply") + if(!check_rights_for(usr.client, R_FUN)) + to_chat(usr, "You feel very stretchy.") - owner.ventcrawler = VENTCRAWLER_ALWAYS + owner.AddElement(/datum/element/ventcrawling, given_tier = VENTCRAWLER_ALWAYS) diff --git a/code/modules/antagonists/blob/blob/overmind.dm b/code/modules/antagonists/blob/blob/overmind.dm index 02be432045..18f6817ec9 100644 --- a/code/modules/antagonists/blob/blob/overmind.dm +++ b/code/modules/antagonists/blob/blob/overmind.dm @@ -277,3 +277,7 @@ GLOBAL_LIST_EMPTY(blob_nodes) var/datum/antagonist/blob/B = mind.has_antag_datum(/datum/antagonist/blob) if(!B) mind.add_antag_datum(/datum/antagonist/blob) + +//the same but it's forced to be allowed by default as cameras usually don't allow emoting +/mob/camera/blob/emote(act, m_type=1, message = null, intentional = FALSE, forced = TRUE) + . = ..() diff --git a/code/modules/antagonists/bloodsucker/bloodsucker_sunlight.dm b/code/modules/antagonists/bloodsucker/bloodsucker_sunlight.dm index 43163f6a70..f26152f90e 100644 --- a/code/modules/antagonists/bloodsucker/bloodsucker_sunlight.dm +++ b/code/modules/antagonists/bloodsucker/bloodsucker_sunlight.dm @@ -7,97 +7,79 @@ // Over Time, tick down toward a "Solar Flare" of UV buffeting the station. This period is harmful to vamps. /obj/effect/sunlight //var/amDay = FALSE - var/cancel_me = FALSE var/amDay = FALSE var/time_til_cycle = 0 - var/nightime_duration = 900 //15 Minutes + var/nighttime_duration = 900 //15 Minutes + var/issued_XP = FALSE /obj/effect/sunlight/Initialize() - countdown() - hud_tick() + . = ..() -/obj/effect/sunlight/proc/countdown() - set waitfor = FALSE +/obj/effect/sunlight/proc/start_countdown() + START_PROCESSING(SSweather, src) //it counts as weather right + time_til_cycle = nighttime_duration - while(!cancel_me) - - time_til_cycle = nightime_duration - - // Part 1: Night (all is well) - while(time_til_cycle > TIME_BLOODSUCKER_DAY_WARN) - sleep(10) - if(cancel_me) - return - //sleep(TIME_BLOODSUCKER_NIGHT - TIME_BLOODSUCKER_DAY_WARN) - warn_daylight(1,"Solar Flares will bombard the station with dangerous UV in [TIME_BLOODSUCKER_DAY_WARN / 60] minutes. Prepare to seek cover in a coffin or closet.") // time2text <-- use Help On - give_home_power() // Give VANISHING ACT power to all vamps with a lair! - - // Part 2: Night Ending - while(time_til_cycle > TIME_BLOODSUCKER_DAY_FINAL_WARN) - sleep(10) - if(cancel_me) - return - //sleep(TIME_BLOODSUCKER_DAY_WARN - TIME_BLOODSUCKER_DAY_FINAL_WARN) - message_admins("BLOODSUCKER NOTICE: Daylight beginning in [TIME_BLOODSUCKER_DAY_FINAL_WARN] seconds.)") - warn_daylight(2,"Solar Flares are about to bombard the station! You have [TIME_BLOODSUCKER_DAY_FINAL_WARN] seconds to find cover!",\ - "In [TIME_BLOODSUCKER_DAY_FINAL_WARN / 10], your master will be at risk of a Solar Flare. Make sure they find cover!") - - // (FINAL LIL WARNING) - while(time_til_cycle > 5) - sleep(10) - if(cancel_me) - return - //sleep(TIME_BLOODSUCKER_DAY_FINAL_WARN - 50) - warn_daylight(3,"Seek cover, for Sol rises!") - - // Part 3: Night Ending - while(time_til_cycle > 0) - sleep(10) - if(cancel_me) - return - //sleep(50) - warn_daylight(4,"Solar flares bombard the station with deadly UV light!
    Stay in cover for the next [TIME_BLOODSUCKER_DAY / 60] minutes or risk Final Death!",\ - "Solar flares bombard the station with UV light!") - - // Part 4: Day - amDay = TRUE - message_admins("BLOODSUCKER NOTICE: Daylight Beginning (Lasts for [TIME_BLOODSUCKER_DAY / 60] minutes.)") - time_til_cycle = TIME_BLOODSUCKER_DAY - sleep(10) // One second grace period. - //var/daylight_time = TIME_BLOODSUCKER_DAY - var/issued_XP = FALSE - while(time_til_cycle > 0) +/obj/effect/sunlight/process() + // Update all Bloodsucker sunlight huds + for(var/datum/mind/M in SSticker.mode.bloodsuckers) + if(!istype(M) || !istype(M.current)) + continue + var/datum/antagonist/bloodsucker/bloodsuckerdatum = M.has_antag_datum(ANTAG_DATUM_BLOODSUCKER) + if(istype(bloodsuckerdatum)) + bloodsuckerdatum.update_sunlight(max(0, time_til_cycle), amDay) // This pings all HUDs + time_til_cycle-- + if(amDay) + if(time_til_cycle > 0 && time_til_cycle % 4 == 0) punish_vamps() - sleep(TIME_BLOODSUCKER_BURN_INTERVAL) - if(cancel_me) - return - //daylight_time -= TIME_BLOODSUCKER_BURN_INTERVAL - // Issue Level Up! if(!issued_XP && time_til_cycle <= 5) issued_XP = TRUE - vamps_rank_up() + // Cycle through all vamp antags and check if they're inside a closet. + for(var/datum/mind/M in SSticker.mode.bloodsuckers) + if(!istype(M) || !istype(M.current)) + continue + var/datum/antagonist/bloodsucker/bloodsuckerdatum = M.has_antag_datum(ANTAG_DATUM_BLOODSUCKER) + if(istype(bloodsuckerdatum)) + bloodsuckerdatum.RankUp() // Rank up! Must still be in a coffin to level! warn_daylight(5,"The solar flare has ended, and the daylight danger has passed...for now.",\ "The solar flare has ended, and the daylight danger has passed...for now.") amDay = FALSE - day_end() // Remove VANISHING ACT power from all vamps who have it! Clear Warnings (sunlight, locker protection) - nightime_duration += 100 //Each day makes the night a minute longer. - message_admins("BLOODSUCKER NOTICE: Daylight Ended. Resetting to Night (Lasts for [nightime_duration / 60] minutes.)") - - - -/obj/effect/sunlight/proc/hud_tick() - set waitfor = FALSE - while(!cancel_me) - // Update all Bloodsucker sunlight huds + issued_XP = FALSE for(var/datum/mind/M in SSticker.mode.bloodsuckers) if(!istype(M) || !istype(M.current)) continue var/datum/antagonist/bloodsucker/bloodsuckerdatum = M.has_antag_datum(ANTAG_DATUM_BLOODSUCKER) - if(istype(bloodsuckerdatum)) - bloodsuckerdatum.update_sunlight(max(0, time_til_cycle), amDay) // This pings all HUDs - sleep(10) - time_til_cycle -- + if(!istype(bloodsuckerdatum)) + continue + // Reset Warnings + bloodsuckerdatum.warn_sun_locker = FALSE + bloodsuckerdatum.warn_sun_burn = FALSE + // Remove Dawn Powers + for(var/datum/action/bloodsucker/P in bloodsuckerdatum.powers) + if(istype(P, /datum/action/bloodsucker/gohome)) + bloodsuckerdatum.powers -= P + P.Remove(M.current) + nighttime_duration += 100 //Each day makes the night a minute longer. + time_til_cycle = nighttime_duration + message_admins("BLOODSUCKER NOTICE: Daylight Ended. Resetting to Night (Lasts for [nighttime_duration / 60] minutes.)") + else + switch(time_til_cycle) + if(TIME_BLOODSUCKER_DAY_WARN) + //sleep(TIME_BLOODSUCKER_NIGHT - TIME_BLOODSUCKER_DAY_WARN) + warn_daylight(1,"Solar Flares will bombard the station with dangerous UV in [TIME_BLOODSUCKER_DAY_WARN / 60] minutes. Prepare to seek cover in a coffin or closet.") // time2text <-- use Help On + give_home_power() // Give VANISHING ACT power to all vamps with a lair! + if(TIME_BLOODSUCKER_DAY_FINAL_WARN) + message_admins("BLOODSUCKER NOTICE: Daylight beginning in [TIME_BLOODSUCKER_DAY_FINAL_WARN] seconds.)") + warn_daylight(2,"Solar Flares are about to bombard the station! You have [TIME_BLOODSUCKER_DAY_FINAL_WARN] seconds to find cover!",\ + "In [TIME_BLOODSUCKER_DAY_FINAL_WARN / 10], your master will be at risk of a Solar Flare. Make sure they find cover!") + if(5) + warn_daylight(3,"Seek cover, for Sol rises!") + if(0) + warn_daylight(4,"Solar flares bombard the station with deadly UV light!
    Stay in cover for the next [TIME_BLOODSUCKER_DAY / 60] minutes or risk Final Death!",\ + "Solar flares bombard the station with UV light!") + amDay = TRUE + message_admins("BLOODSUCKER NOTICE: Daylight Beginning (Lasts for [TIME_BLOODSUCKER_DAY / 60] minutes.)") + time_til_cycle = TIME_BLOODSUCKER_DAY /obj/effect/sunlight/proc/warn_daylight(danger_level =0, vampwarn = "", vassalwarn = "") for(var/datum/mind/M in SSticker.mode.bloodsuckers) @@ -161,32 +143,6 @@ M.current.updatehealth() SEND_SIGNAL(M.current, COMSIG_ADD_MOOD_EVENT, "vampsleep", /datum/mood_event/daylight_2) -/obj/effect/sunlight/proc/day_end() - for(var/datum/mind/M in SSticker.mode.bloodsuckers) - if(!istype(M) || !istype(M.current)) - continue - var/datum/antagonist/bloodsucker/bloodsuckerdatum = M.has_antag_datum(ANTAG_DATUM_BLOODSUCKER) - if(!istype(bloodsuckerdatum)) - continue - // Reset Warnings - bloodsuckerdatum.warn_sun_locker = FALSE - bloodsuckerdatum.warn_sun_burn = FALSE - // Remove Dawn Powers - for(var/datum/action/bloodsucker/P in bloodsuckerdatum.powers) - if(istype(P, /datum/action/bloodsucker/gohome)) - bloodsuckerdatum.powers -= P - P.Remove(M.current) - -/obj/effect/sunlight/proc/vamps_rank_up() - set waitfor = FALSE - // Cycle through all vamp antags and check if they're inside a closet. - for(var/datum/mind/M in SSticker.mode.bloodsuckers) - if(!istype(M) || !istype(M.current)) - continue - var/datum/antagonist/bloodsucker/bloodsuckerdatum = M.has_antag_datum(ANTAG_DATUM_BLOODSUCKER) - if(istype(bloodsuckerdatum)) - bloodsuckerdatum.RankUp() // Rank up! Must still be in a coffin to level! - /obj/effect/sunlight/proc/give_home_power() // It's late...! Give the "Vanishing Act" gohome power to bloodsuckers. for(var/datum/mind/M in SSticker.mode.bloodsuckers) diff --git a/code/modules/antagonists/bloodsucker/datum_vassal.dm b/code/modules/antagonists/bloodsucker/datum_vassal.dm index c065d5fe7c..91e89e71e6 100644 --- a/code/modules/antagonists/bloodsucker/datum_vassal.dm +++ b/code/modules/antagonists/bloodsucker/datum_vassal.dm @@ -90,7 +90,7 @@ /datum/antagonist/vassal/greet() to_chat(owner, "You are now the mortal servant of [master.owner.current], a bloodsucking vampire!") to_chat(owner, "The power of [master.owner.current.p_their()] immortal blood compells you to obey [master.owner.current.p_them()] in all things, even offering your own life to prolong theirs.
    \ - You are not required to obey any other Bloodsucker, for only [master.owner.current] is your master. The laws of Nanotransen do not apply to you now; only your vampiric master's word must be obeyed.") + You are not required to obey any other Bloodsucker, for only [master.owner.current] is your master. The laws of Nanotrasen do not apply to you now; only your vampiric master's word must be obeyed.") // Effects... owner.current.playsound_local(null, 'sound/magic/mutate.ogg', 100, FALSE, pressure_affected = FALSE) //owner.store_memory("You became the mortal servant of [master.owner.current], a bloodsucking vampire!") diff --git a/code/modules/antagonists/bloodsucker/items/bloodsucker_stake.dm b/code/modules/antagonists/bloodsucker/items/bloodsucker_stake.dm index 2aed251189..69410027a4 100644 --- a/code/modules/antagonists/bloodsucker/items/bloodsucker_stake.dm +++ b/code/modules/antagonists/bloodsucker/items/bloodsucker_stake.dm @@ -43,14 +43,13 @@ // This exists so Hardened/Silver Stake can't have a welding torch used on them. /obj/item/stake/basic/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/weldingtool)) + if(W.tool_behaviour == TOOL_WELDER) //if (amWelded) // to_chat(user, "This stake has already been treated with fire.") // return //amWelded = TRUE // Weld it - var/obj/item/weldingtool/WT = W - if(WT.use(0))//remove_fuel(0,user)) + if(W.use(0))//remove_fuel(0,user)) user.visible_message("[user.name] scorched the pointy end of [src] with the welding tool.", \ "You scorch the pointy end of [src] with the welding tool.", \ "You hear welding.") diff --git a/code/modules/antagonists/bloodsucker/objects/bloodsucker_coffin.dm b/code/modules/antagonists/bloodsucker/objects/bloodsucker_coffin.dm index a555677719..881da8f282 100644 --- a/code/modules/antagonists/bloodsucker/objects/bloodsucker_coffin.dm +++ b/code/modules/antagonists/bloodsucker/objects/bloodsucker_coffin.dm @@ -164,11 +164,11 @@ if(istype(W, cutting_tool)) to_chat(user, "This is a much more complex mechanical structure than you thought. You don't know where to begin cutting [src].") return - else if(anchored && istype(W, /obj/item/wrench)) // Can't unanchor unless owner. + else if(anchored && W.tool_behaviour == TOOL_WRENCH) // Can't unanchor unless owner. to_chat(user, "The coffin won't come unanchored from the floor.") return - if(locked && istype(W, /obj/item/crowbar)) + if(locked && W.tool_behaviour == TOOL_CROWBAR) var/pry_time = pryLidTimer * W.toolspeed // Pry speed must be affected by the speed of the tool. user.visible_message("[user] tries to pry the lid off of [src] with [W].", \ "You begin prying the lid off of [src] with [W]. This should take about [DisplayTimeText(pry_time)].") diff --git a/code/modules/antagonists/brother/brother.dm b/code/modules/antagonists/brother/brother.dm index 0a2e079921..30e6a80e85 100644 --- a/code/modules/antagonists/brother/brother.dm +++ b/code/modules/antagonists/brother/brother.dm @@ -149,8 +149,6 @@ if(prob(50)) if(LAZYLEN(active_ais()) && prob(100/GLOB.joined_player_list.len)) add_objective(new/datum/objective/destroy, TRUE) - else if(prob(30)) - add_objective(new/datum/objective/maroon, TRUE) else add_objective(new/datum/objective/assassinate, TRUE) else diff --git a/code/modules/antagonists/changeling/changeling.dm b/code/modules/antagonists/changeling/changeling.dm index dacd568993..9c0b19cd83 100644 --- a/code/modules/antagonists/changeling/changeling.dm +++ b/code/modules/antagonists/changeling/changeling.dm @@ -213,7 +213,7 @@ if(canrespec) to_chat(owner.current, "We have removed our evolutions from this form, and are now ready to readapt.") reset_powers() - playsound(get_turf(owner.current), 'sound/effects/lingreadapt.ogg', 75, TRUE, 5, soundenvwet = 0) + playsound(get_turf(owner.current), 'sound/effects/lingreadapt.ogg', 75, TRUE, 5) canrespec = 0 SSblackbox.record_feedback("tally", "changeling_power_purchase", 1, "Readapt") return 1 @@ -433,30 +433,21 @@ destroy_objective.find_target() objectives += destroy_objective else - if(prob(70)) - var/datum/objective/assassinate/once/kill_objective = new - kill_objective.owner = owner - if(team_mode) //No backstabbing while in a team - kill_objective.find_target_by_role(role = ROLE_CHANGELING, role_type = 1, invert = 1) - else - kill_objective.find_target() - objectives += kill_objective + var/datum/objective/assassinate/once/kill_objective = new + kill_objective.owner = owner + if(team_mode) //No backstabbing while in a team + kill_objective.find_target_by_role(role = ROLE_CHANGELING, role_type = 1, invert = 1) else - var/datum/objective/maroon/maroon_objective = new - maroon_objective.owner = owner - if(team_mode) - maroon_objective.find_target_by_role(role = ROLE_CHANGELING, role_type = 1, invert = 1) - else - maroon_objective.find_target() - objectives += maroon_objective + kill_objective.find_target() + objectives += kill_objective - if (!(locate(/datum/objective/escape) in objectives) && escape_objective_possible) - var/datum/objective/escape/escape_with_identity/identity_theft = new - identity_theft.owner = owner - identity_theft.target = maroon_objective.target - identity_theft.update_explanation_text() - objectives += identity_theft - escape_objective_possible = FALSE + if(!(locate(/datum/objective/escape) in objectives) && escape_objective_possible && prob(50)) + var/datum/objective/escape/escape_with_identity/identity_theft = new + identity_theft.owner = owner + identity_theft.target = kill_objective.target + identity_theft.update_explanation_text() + objectives += identity_theft + escape_objective_possible = FALSE if (!(locate(/datum/objective/escape) in objectives) && escape_objective_possible) if(prob(50)) diff --git a/code/modules/antagonists/changeling/powers/shriek.dm b/code/modules/antagonists/changeling/powers/shriek.dm index 0e2515fdae..dae1da9f8f 100644 --- a/code/modules/antagonists/changeling/powers/shriek.dm +++ b/code/modules/antagonists/changeling/powers/shriek.dm @@ -29,7 +29,7 @@ for(var/obj/machinery/light/L in range(4, user)) L.on = 1 L.break_light_tube() - playsound(get_turf(user), 'sound/effects/lingscreech.ogg', 75, TRUE, 5, soundenvwet = 0) + playsound(get_turf(user), 'sound/effects/lingscreech.ogg', 75, TRUE, 5) return TRUE /obj/effect/proc_holder/changeling/dissonant_shriek @@ -49,5 +49,5 @@ L.on = 1 L.break_light_tube() empulse_using_range(get_turf(user), 8, TRUE) - playsound(get_turf(user), 'sound/effects/lingempscreech.ogg', 75, TRUE, 5, soundenvwet = 0) + playsound(get_turf(user), 'sound/effects/lingempscreech.ogg', 75, TRUE, 5) return TRUE diff --git a/code/modules/antagonists/clockcult/clock_effects/city_of_cogs_rift.dm b/code/modules/antagonists/clockcult/clock_effects/city_of_cogs_rift.dm index 3ea4668df8..1da49efe3c 100644 --- a/code/modules/antagonists/clockcult/clock_effects/city_of_cogs_rift.dm +++ b/code/modules/antagonists/clockcult/clock_effects/city_of_cogs_rift.dm @@ -22,7 +22,7 @@ for(var/mob/M in GLOB.player_list) if(M.z == z) if(get_dist(src, M) >= 7) - M.playsound_local(src, 'sound/magic/blink.ogg', 10, FALSE, falloff = 10) + M.playsound_local(src, 'sound/magic/blink.ogg', 10, FALSE) else M.playsound_local(src, 'sound/magic/blink.ogg', 50, FALSE) diff --git a/code/modules/antagonists/clockcult/clock_helpers/clock_rites.dm b/code/modules/antagonists/clockcult/clock_helpers/clock_rites.dm index 59a86ffa34..58ee73ef5b 100644 --- a/code/modules/antagonists/clockcult/clock_helpers/clock_rites.dm +++ b/code/modules/antagonists/clockcult/clock_helpers/clock_rites.dm @@ -165,14 +165,16 @@ /datum/clockwork_rite/treat_wounds/cast(var/mob/living/invoker, var/turf/T, var/mob/living/carbon/human/target) if(!target) return FALSE - if(!target.all_wounds.len) + if(!target.all_wounds || !target.all_wounds.len) to_chat(invoker, "This one does not require mending.") return FALSE .= ..() if(!.) return FALSE target.adjustToxLoss(10 * target.all_wounds.len) - QDEL_LIST(target.all_wounds) + for(var/i in target.all_wounds) + var/datum/wound/mended = i + mended.remove_wound() to_chat(target, "You feel your wounds heal, but are overcome with deep nausea.") new /obj/effect/temp_visual/ratvar/sigil/vitality(T) diff --git a/code/modules/antagonists/clockcult/clock_helpers/scripture_checks.dm b/code/modules/antagonists/clockcult/clock_helpers/scripture_checks.dm index 66e20b6e87..53e70e1404 100644 --- a/code/modules/antagonists/clockcult/clock_helpers/scripture_checks.dm +++ b/code/modules/antagonists/clockcult/clock_helpers/scripture_checks.dm @@ -19,7 +19,7 @@ update_slab_info() for(var/mob/M in GLOB.player_list) if(is_servant_of_ratvar(M) || isobserver(M)) - M.playsound_local(M, 'sound/magic/clockwork/scripture_tier_up.ogg', 50, FALSE, pressure_affected = FALSE) + M.playsound_local(M, 'sound/magic/clockwork/scripture_tier_up.ogg', 20, FALSE, pressure_affected = FALSE) /proc/update_slab_info(obj/item/clockwork/slab/set_slab) generate_all_scripture() diff --git a/code/modules/antagonists/clockcult/clock_items/clock_weapons/ratvarian_shield.dm b/code/modules/antagonists/clockcult/clock_items/clock_weapons/ratvarian_shield.dm index a681e9b38b..fb7b4f8a94 100644 --- a/code/modules/antagonists/clockcult/clock_items/clock_weapons/ratvarian_shield.dm +++ b/code/modules/antagonists/clockcult/clock_items/clock_weapons/ratvarian_shield.dm @@ -22,7 +22,7 @@ . = ..() desc = initial(desc) -obj/item/shield/riot/ratvarian/proc/calc_bash_mult() +/obj/item/shield/riot/ratvarian/proc/calc_bash_mult() var/bash_mult = 0 if(!dam_absorbed) return 1 diff --git a/code/modules/antagonists/clockcult/clock_items/clockwork_slab.dm b/code/modules/antagonists/clockcult/clock_items/clockwork_slab.dm index 571a817024..8060b7b0cd 100644 --- a/code/modules/antagonists/clockcult/clock_items/clockwork_slab.dm +++ b/code/modules/antagonists/clockcult/clock_items/clockwork_slab.dm @@ -21,8 +21,10 @@ var/recollecting = TRUE //if we're looking at fancy recollection. tutorial enabled by default var/recollection_category = "Default" - var/list/quickbound = list(/datum/clockwork_scripture/spatial_gateway, \ - /datum/clockwork_scripture/ranged_ability/kindle, /datum/clockwork_scripture/ranged_ability/hateful_manacles) //quickbound scripture, accessed by index + var/list/quickbound = list( + /datum/clockwork_scripture/spatial_gateway, + /datum/clockwork_scripture/ranged_ability/kindle, + /datum/clockwork_scripture/ranged_ability/hateful_manacles) //quickbound scripture, accessed by index var/maximum_quickbound = 5 //how many quickbound scriptures we can have var/obj/structure/destructible/clockwork/trap/linking //If we're linking traps together, which ones we're doing @@ -203,10 +205,10 @@ to_chat(user, "You need to hold the slab in your active hand to recite scripture!") return FALSE var/initial_tier = initial(scripture.tier) - if(initial_tier == SCRIPTURE_PERIPHERAL) + if(initial_tier == SCRIPTURE_PERIPHERAL && !issilicon(user)) //Silicons use peripheral scripture & cannot open the slab. to_chat(user, "Nice try using href exploits") return - if(!GLOB.ratvar_awakens && !no_cost && !SSticker.scripture_states[initial_tier]) + if(!GLOB.ratvar_awakens && !no_cost && !SSticker.scripture_states[initial_tier] &&!issilicon(user)) //silicons can't choose their spells, so lets allow them to always cast their assigned ones. to_chat(user, "That scripture is not unlocked, and cannot be recited!") return FALSE var/datum/clockwork_scripture/scripture_to_recite = new scripture @@ -326,6 +328,7 @@ "requirement" = "Unlock powerful equipment and structures by converting five servants or if [DisplayPower(JUDGEMENT_UNLOCK_THRESHOLD)] of power is reached..", "ready" = SSticker.scripture_states[SCRIPTURE_JUDGEMENT] ) + // no need to learn shit, ratvar is free .["recollection_categories"] = list() if(GLOB.ratvar_awakens) return @@ -340,19 +343,25 @@ ) .["rec_section"] = get_recollection(recollection_category) generate_all_scripture() - //needs a new place to live, preferably when clockcult unlocks/downgrades a tier. Smart enough to earlyreturn. + //needs a new place to live, preferably when clockcult unlocks/downgrades a tier. + //comsig maybe? /obj/item/clockwork/slab/ui_act(action, params) + . = ..() + if(.) + return switch(action) if("toggle") recollecting = !recollecting + . = TRUE if("recite") INVOKE_ASYNC(src, .proc/recite_scripture, text2path(params["script"]), usr, FALSE) + . = TRUE if("bind") var/datum/clockwork_scripture/path = text2path(params["script"]) //we need a path and not a string if(!ispath(path, /datum/clockwork_scripture) || !initial(path.quickbind) || initial(path.tier) == SCRIPTURE_PERIPHERAL) //fuck you href bus to_chat(usr, "Nice try using href exploits") - return + return FALSE var/found_index = quickbound.Find(path) if(found_index) //hey, we already HAVE this bound if(LAZYLEN(quickbound) == found_index) //if it's the last scripture, remove it instead of leaving a null @@ -361,6 +370,7 @@ quickbound[found_index] = null //otherwise, leave it as a null so the scripture maintains position update_quickbind() else + // todo: async this due to ((input)) but its fine for now var/target_index = input("Position of [initial(path.name)], 1 to [maximum_quickbound]?", "Input") as num|null if(isnum(target_index) && target_index > 0 && target_index <= maximum_quickbound && !..()) var/datum/clockwork_scripture/S @@ -368,10 +378,11 @@ S = quickbound[target_index] if(S != path) quickbind_to_slot(path, target_index) + . = TRUE if("rec_category") recollection_category = params["category"] update_static_data() - return TRUE + . = TRUE /obj/item/clockwork/slab/proc/quickbind_to_slot(datum/clockwork_scripture/scripture, index) //takes a typepath(typecast for initial()) and binds it to a slot if(!ispath(scripture) || !scripture || (scripture in quickbound)) diff --git a/code/modules/antagonists/clockcult/clock_scriptures/scripture_applications.dm b/code/modules/antagonists/clockcult/clock_scriptures/scripture_applications.dm index cbf3bdaa38..77dc174238 100644 --- a/code/modules/antagonists/clockcult/clock_scriptures/scripture_applications.dm +++ b/code/modules/antagonists/clockcult/clock_scriptures/scripture_applications.dm @@ -8,7 +8,7 @@ descname = "Powers Nearby Structures" name = "Sigil of Transmission" desc = "Places a sigil that can drain and will store energy to power clockwork structures." - invocations = list("Divinity...", "...power our creations!") + invocations = list("Divinity...", "...power our creations.") channel_time = 70 power_cost = 200 whispered = TRUE @@ -28,7 +28,7 @@ descname = "Powered Structure, Delay Emergency Shuttles" name = "Prolonging Prism" desc = "Creates a mechanized prism which will delay the arrival of an emergency shuttle by 2 minutes at a massive power cost." - invocations = list("May this prism...", "...grant us time to enact his will!") + invocations = list("May this prism...", "...grant us time to enact his will.") channel_time = 80 power_cost = 300 object_path = /obj/structure/destructible/clockwork/powered/prolonging_prism @@ -60,7 +60,7 @@ descname = "Powered Structure, Area Denial" name = "Mania Motor" desc = "Creates a mania motor which causes minor damage and a variety of negative mental effects in nearby non-Servant humans, potentially up to and including conversion." - invocations = list("May this transmitter...", "...break the will of all who oppose us!") + invocations = list("May this transmitter...", "...break the will of all who oppose us.") channel_time = 80 power_cost = 750 object_path = /obj/structure/destructible/clockwork/powered/mania_motor @@ -83,7 +83,7 @@ descname = "Powered Structure, Teleportation Hub" name = "Clockwork Obelisk" desc = "Creates a clockwork obelisk that can broadcast messages over the Hierophant Network or open a Spatial Gateway to any living Servant or clockwork obelisk." - invocations = list("May this obelisk...", "...take us to all places!") + invocations = list("May this obelisk...", "...take us to all places.") channel_time = 80 power_cost = 300 object_path = /obj/structure/destructible/clockwork/powered/clockwork_obelisk @@ -163,7 +163,7 @@ descname = "Well-Rounded Combat Construct" name = "Clockwork Marauder" desc = "Creates a shell for a clockwork marauder, a balanced frontline construct that can deflect projectiles with its shield." - invocations = list("Arise, avatar of Arbiter!", "Defend the Ark with vengeful zeal.") + invocations = list("Arise, avatar of Arbiter!", "Defend the Ark with vengeful zeal!") channel_time = 80 power_cost = 8000 creator_message = "Your slab disgorges several chunks of replicant alloy that form into a suit of thrumming armor." diff --git a/code/modules/antagonists/clockcult/clock_scriptures/scripture_drivers.dm b/code/modules/antagonists/clockcult/clock_scriptures/scripture_drivers.dm index 0a59656e31..b79bcfa03d 100644 --- a/code/modules/antagonists/clockcult/clock_scriptures/scripture_drivers.dm +++ b/code/modules/antagonists/clockcult/clock_scriptures/scripture_drivers.dm @@ -7,7 +7,7 @@ descname = "Generates Power From Starlight" name = "Stargazer" desc = "Forms a weak structure that generates power every second while within three tiles of starlight." - invocations = list("Capture their inferior light for us!") + invocations = list("Capture their inferior light for us.") channel_time = 50 power_cost = 200 object_path = /obj/structure/destructible/clockwork/stargazer @@ -16,6 +16,7 @@ usage_tip = "For obvious reasons, make sure to place this near a window or somewhere else that can see space!" tier = SCRIPTURE_DRIVER one_per_tile = TRUE + whispered = TRUE primary_component = HIEROPHANT_ANSIBLE sort_priority = 1 quickbind = TRUE @@ -34,7 +35,7 @@ descname = "Power Generation" name = "Integration Cog" desc = "Fabricates an integration cog, which can be used on an open APC to replace its innards and passively siphon its power." - invocations = list("Take that which sustains them!") + invocations = list("Take that which sustains them.") channel_time = 10 power_cost = 10 whispered = TRUE @@ -55,7 +56,7 @@ descname = "Trap, Stunning" name = "Sigil of Transgression" desc = "Wards a tile with a sigil, which will briefly stun the next non-Servant to cross it and apply Belligerent to them." - invocations = list("Divinity, smite...", "...those who trespass here!") + invocations = list("Divinity, smite...", "...those who trespass here.") channel_time = 50 power_cost = 50 whispered = TRUE @@ -75,7 +76,7 @@ descname = "Trap, Conversion" name = "Sigil of Submission" desc = "Places a luminous sigil that will convert any non-Servants that remain on it for 8 seconds." - invocations = list("Divinity, enlighten...", "...those who trespass here!") + invocations = list("Divinity, enlighten...", "...those who trespass here.") channel_time = 60 power_cost = 125 whispered = TRUE @@ -95,7 +96,7 @@ descname = "Short-Range Single-Target Stun" name = "Kindle" desc = "Charges your slab with divine energy, allowing you to overwhelm a target with Ratvar's light." - invocations = list("Divinity, show them your light!") + invocations = list("Divinity, show them your light.") whispered = TRUE channel_time = 25 //2.5 seconds should be a okay compromise between being able to use it when needed, and not being able to just pause in combat for a second and hardstunning your enemy power_cost = 125 @@ -118,7 +119,7 @@ descname = "Handcuffs" name = "Hateful Manacles" desc = "Forms replicant manacles around a target's wrists that function like handcuffs." - invocations = list("Shackle the heretic!", "Break them in body and spirit!") + invocations = list("Shackle the heretic!", "Break them in body and spirit.") channel_time = 15 power_cost = 25 whispered = TRUE @@ -269,7 +270,7 @@ descname = "New Clockwork Slab" name = "Replicant" desc = "Creates a new clockwork slab." - invocations = list("Metal, become greater!") + invocations = list("Metal, become greater.") channel_time = 10 power_cost = 25 whispered = TRUE @@ -290,7 +291,7 @@ descname = "Limited Xray Vision Glasses" name = "Wraith Spectacles" desc = "Fabricates a pair of glasses which grant true sight but cause gradual vision loss." - invocations = list("Show the truth of this world to me!") + invocations = list("Show the truth of this world to me.") channel_time = 10 power_cost = 50 whispered = TRUE @@ -310,7 +311,7 @@ name = "Spatial Gateway" desc = "Tears open a miniaturized gateway in spacetime to any conscious servant that can transport objects or creatures to its destination. \ Each servant assisting in the invocation adds one additional use and four additional seconds to the gateway's uses and duration." - invocations = list("Spatial Gateway...", "...activate!") + invocations = list("Spatial Gateway...", "...activate.") channel_time = 30 power_cost = 400 whispered = TRUE diff --git a/code/modules/antagonists/clockcult/clock_scriptures/scripture_scripts.dm b/code/modules/antagonists/clockcult/clock_scriptures/scripture_scripts.dm index b559b34d5e..7ba4ce0936 100644 --- a/code/modules/antagonists/clockcult/clock_scriptures/scripture_scripts.dm +++ b/code/modules/antagonists/clockcult/clock_scriptures/scripture_scripts.dm @@ -29,7 +29,7 @@ descname = "Structure, Turret" name = "Ocular Warden" desc = "Forms an automatic short-range turret which will automatically attack nearby unrestrained non-Servants that can see it." - invocations = list("Guardians of Engine...", "...judge those who would harm us!") + invocations = list("Guardians of Engine...", "...judge those who would harm us.") channel_time = 100 power_cost = 250 object_path = /obj/structure/destructible/clockwork/ocular_warden @@ -105,7 +105,7 @@ descname = "Delayed Area Knockdown Glasses" name = "Judicial Visor" desc = "Creates a visor that can smite an area, applying Belligerent and briefly stunning. The smote area will explode after 3 seconds." - invocations = list("Grant me the flames of Engine!") + invocations = list("Grant me the flames of Engine.") channel_time = 10 power_cost = 400 whispered = TRUE @@ -124,7 +124,7 @@ descname = "Shield with empowerable bashes" name = "Nezbere's shield" desc = "Creates a shield which generates charge from blocking damage, using it to empower its bashes tremendously. It is repaired with brass, and while very durable, extremely weak to lasers and, even more so, to energy weaponry." - invocations = list("Shield me...", "... from the coming dark!") + invocations = list("Shield me...", "... from the coming dark.") channel_time = 20 power_cost = 600 //Shouldn't be too spammable but not too hard to get either whispered = TRUE @@ -143,7 +143,7 @@ descname = "Summonable Armor and Weapons" name = "Clockwork Armaments" desc = "Allows the invoker to summon clockwork armor and a Ratvarian spear at will. The spear's attacks will generate Vitality, used for healing." - invocations = list("Grant me armaments...", "...from the forge of Armorer!") + invocations = list("Grant me armaments...", "...from the forge of Armorer.") channel_time = 20 power_cost = 250 whispered = TRUE diff --git a/code/modules/antagonists/clockcult/clock_structure.dm b/code/modules/antagonists/clockcult/clock_structure.dm index 2464015b6b..380e93f102 100644 --- a/code/modules/antagonists/clockcult/clock_structure.dm +++ b/code/modules/antagonists/clockcult/clock_structure.dm @@ -95,7 +95,7 @@ return ..() /obj/structure/destructible/clockwork/attackby(obj/item/I, mob/user, params) - if(is_servant_of_ratvar(user) && istype(I, /obj/item/wrench) && unanchored_icon) + if(is_servant_of_ratvar(user) && I.tool_behaviour == TOOL_WRENCH && unanchored_icon) if(default_unfasten_wrench(user, I, 50) == SUCCESSFUL_UNFASTEN) update_anchored(user) return 1 diff --git a/code/modules/antagonists/clockcult/clock_structures/ark_of_the_clockwork_justicar.dm b/code/modules/antagonists/clockcult/clock_structures/ark_of_the_clockwork_justicar.dm index 7478d45b08..3b507fb57c 100644 --- a/code/modules/antagonists/clockcult/clock_structures/ark_of_the_clockwork_justicar.dm +++ b/code/modules/antagonists/clockcult/clock_structures/ark_of_the_clockwork_justicar.dm @@ -149,7 +149,7 @@ transform = matrix() * 2 animate(src, transform = matrix() * 0.5, time = 30, flags = ANIMATION_END_NOW) -obj/structure/destructible/clockwork/massive/celestial_gateway/Destroy() +/obj/structure/destructible/clockwork/massive/celestial_gateway/Destroy() STOP_PROCESSING(SSprocessing, src) if(!purpose_fulfilled) var/area/gate_area = get_area(src) diff --git a/code/modules/antagonists/clockcult/clock_structures/trap_triggers/pressure_sensor.dm b/code/modules/antagonists/clockcult/clock_structures/trap_triggers/pressure_sensor.dm index af29bc9b31..c5d1bc2ded 100644 --- a/code/modules/antagonists/clockcult/clock_structures/trap_triggers/pressure_sensor.dm +++ b/code/modules/antagonists/clockcult/clock_structures/trap_triggers/pressure_sensor.dm @@ -15,3 +15,5 @@ audible_message("*click*") playsound(src, 'sound/items/screwdriver2.ogg', 50, TRUE) activate() + + . = ..() diff --git a/code/modules/antagonists/clockcult/clock_structures/trap_triggers/pressure_sensor_mech.dm b/code/modules/antagonists/clockcult/clock_structures/trap_triggers/pressure_sensor_mech.dm index 10a5b7787f..21735ad6d7 100644 --- a/code/modules/antagonists/clockcult/clock_structures/trap_triggers/pressure_sensor_mech.dm +++ b/code/modules/antagonists/clockcult/clock_structures/trap_triggers/pressure_sensor_mech.dm @@ -8,7 +8,7 @@ alpha = 75 /obj/structure/destructible/clockwork/trap/trigger/pressure_sensor/mech/Crossed(atom/movable/AM) - + . = ..() if(!istype(AM,/obj/mecha/)) return diff --git a/code/modules/antagonists/clockcult/clock_structures/traps/steam_vent.dm b/code/modules/antagonists/clockcult/clock_structures/traps/steam_vent.dm index 6aede1592e..7d6eea1c79 100644 --- a/code/modules/antagonists/clockcult/clock_structures/traps/steam_vent.dm +++ b/code/modules/antagonists/clockcult/clock_structures/traps/steam_vent.dm @@ -21,3 +21,5 @@ if(isliving(AM) && opacity) var/mob/living/L = AM L.adjust_fire_stacks(-1) //It's wet! + return + . = ..() diff --git a/code/modules/antagonists/clockcult/clock_structures/wall_gear.dm b/code/modules/antagonists/clockcult/clock_structures/wall_gear.dm index d823f19d4a..7e4822b622 100644 --- a/code/modules/antagonists/clockcult/clock_structures/wall_gear.dm +++ b/code/modules/antagonists/clockcult/clock_structures/wall_gear.dm @@ -23,10 +23,10 @@ return /obj/structure/destructible/clockwork/wall_gear/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/wrench)) + if(I.tool_behaviour == TOOL_WRENCH) default_unfasten_wrench(user, I, 10) return 1 - else if(istype(I, /obj/item/screwdriver)) + else if(I.tool_behaviour == TOOL_SCREWDRIVER) if(anchored) to_chat(user, "[src] needs to be unsecured to disassemble it!") else diff --git a/code/modules/antagonists/clockcult/clockcult.dm b/code/modules/antagonists/clockcult/clockcult.dm index b6ed7dfe65..b935258c27 100644 --- a/code/modules/antagonists/clockcult/clockcult.dm +++ b/code/modules/antagonists/clockcult/clockcult.dm @@ -15,13 +15,16 @@ var/ignore_holy_water = FALSE /datum/antagonist/clockcult/silent + name = "Silent Clock Cultist" silent = TRUE show_in_antagpanel = FALSE //internal /datum/antagonist/clockcult/neutered + name = "Neutered Clock Cultist" neutered = TRUE /datum/antagonist/clockcult/neutered/traitor + name = "Traitor Clock Cultist" ignore_eligibility_check = TRUE ignore_holy_water = TRUE show_in_roundend = FALSE @@ -185,7 +188,7 @@ /datum/antagonist/clockcult/admin_add(datum/mind/new_owner,mob/admin) - add_servant_of_ratvar(new_owner.current, TRUE) + add_servant_of_ratvar(new_owner.current, TRUE, override_type = type) message_admins("[key_name_admin(admin)] has made [new_owner.current] into a servant of Ratvar.") log_admin("[key_name(admin)] has made [new_owner.current] into a servant of Ratvar.") diff --git a/code/modules/antagonists/cult/cult.dm b/code/modules/antagonists/cult/cult.dm index a2ec4a47a4..09d8771a62 100644 --- a/code/modules/antagonists/cult/cult.dm +++ b/code/modules/antagonists/cult/cult.dm @@ -19,9 +19,11 @@ var/ignore_holy_water = FALSE /datum/antagonist/cult/neutered + name = "Neutered Cultist" neutered = TRUE /datum/antagonist/cult/neutered/traitor + name = "Traitor Cultist" ignore_eligibility_checks = TRUE ignore_holy_water = TRUE show_in_roundend = FALSE diff --git a/code/modules/antagonists/devil/true_devil/_true_devil.dm b/code/modules/antagonists/devil/true_devil/_true_devil.dm index 293b1de3dc..069080170e 100644 --- a/code/modules/antagonists/devil/true_devil/_true_devil.dm +++ b/code/modules/antagonists/devil/true_devil/_true_devil.dm @@ -11,7 +11,6 @@ gender = NEUTER health = 350 maxHealth = 350 - ventcrawler = VENTCRAWLER_NONE density = TRUE pass_flags = 0 sight = (SEE_TURFS | SEE_OBJS) diff --git a/code/modules/antagonists/devil/true_devil/inventory.dm b/code/modules/antagonists/devil/true_devil/inventory.dm index 579dfb4fb1..e98cb5ca72 100644 --- a/code/modules/antagonists/devil/true_devil/inventory.dm +++ b/code/modules/antagonists/devil/true_devil/inventory.dm @@ -1,4 +1,4 @@ -/mob/living/carbon/true_devil/doUnEquip(obj/item/I, force) +/mob/living/carbon/true_devil/doUnEquip(obj/item/I, force, silent = FALSE) if(..()) update_inv_hands() return 1 diff --git a/code/modules/antagonists/disease/disease_abilities.dm b/code/modules/antagonists/disease/disease_abilities.dm index 496d11bcbc..fc53575bd8 100644 --- a/code/modules/antagonists/disease/disease_abilities.dm +++ b/code/modules/antagonists/disease/disease_abilities.dm @@ -191,8 +191,6 @@ new /datum/disease_ability/symptom/powerful/youth /datum/disease_ability/action/sneeze name = "Voluntary Sneezing" actions = list(/datum/action/cooldown/disease_sneeze) - cost = 2 - required_total_points = 3 short_desc = "Force the host you are following to sneeze, spreading your infection to those in front of them." long_desc = "Force the host you are following to sneeze with extra force, spreading your infection to any victims in a 4 meter cone in front of your host.
    Cooldown: 20 seconds" @@ -229,8 +227,6 @@ new /datum/disease_ability/symptom/powerful/youth /datum/disease_ability/action/infect name = "Secrete Infection" actions = list(/datum/action/cooldown/disease_infect) - cost = 2 - required_total_points = 3 short_desc = "Cause all objects your host is touching to become infectious for a limited time, spreading your infection to anyone who touches them." long_desc = "Cause the host you are following to excrete an infective substance from their pores, causing all objects touching their skin to transmit your infection to anyone who touches them for the next 30 seconds. This includes the floor, if they are not wearing shoes, and any items they are holding, if they are not wearing gloves.
    Cooldown: 40 seconds" @@ -271,23 +267,20 @@ new /datum/disease_ability/symptom/powerful/youth //healing costs more so you have to techswitch from naughty disease otherwise we'd have friendly disease for easy greentext (no fun!) /datum/disease_ability/symptom/mild - cost = 2 - required_total_points = 4 category = "Symptom (Weak)" /datum/disease_ability/symptom/medium - cost = 4 - required_total_points = 8 category = "Symptom" /datum/disease_ability/symptom/medium/heal cost = 5 + required_total_points = 5 malefit = -1 category = "Symptom (+)" /datum/disease_ability/symptom/powerful cost = 4 - required_total_points = 16 + required_total_points = 10 category = "Symptom (Strong)" /datum/disease_ability/symptom/powerful/heal diff --git a/code/modules/antagonists/disease/disease_disease.dm b/code/modules/antagonists/disease/disease_disease.dm index c37abefab0..5e98962740 100644 --- a/code/modules/antagonists/disease/disease_disease.dm +++ b/code/modules/antagonists/disease/disease_disease.dm @@ -5,6 +5,7 @@ viable_mobtypes = list(/mob/living/carbon/human) mutable = FALSE var/mob/camera/disease/overmind + infectable_biotypes = MOB_ORGANIC|MOB_ROBOTIC /datum/disease/advance/sentient_disease/New() ..() diff --git a/code/modules/antagonists/eldritch_cult/eldritch_antag.dm b/code/modules/antagonists/eldritch_cult/eldritch_antag.dm index ea226a3cb6..1e43754477 100644 --- a/code/modules/antagonists/eldritch_cult/eldritch_antag.dm +++ b/code/modules/antagonists/eldritch_cult/eldritch_antag.dm @@ -10,6 +10,8 @@ var/give_equipment = TRUE var/list/researched_knowledge = list() var/total_sacrifices = 0 + var/list/sac_targetted = list() //Which targets did living hearts give them, but they did not sac? + var/list/actually_sacced = list() //Which targets did they actually sac? var/ascended = FALSE /datum/antagonist/heretic/admin_add(datum/mind/new_owner,mob/admin) @@ -175,6 +177,17 @@ knowledge_message += "[EK.name]" parts += knowledge_message.Join(", ") + parts += "Targets assigned by living hearts, but not sacrificed:" + if(!sac_targetted.len) + parts += "None." + else + parts += sac_targetted.Join(",") + parts += "Sacrifices performed:" + if(!actually_sacced.len) + parts += "None!" + else + parts += actually_sacced.Join(",") + return parts.Join("
    ") //////////////// // Knowledge // @@ -213,6 +226,23 @@ if(ascended) . += 20 +/datum/antagonist/heretic/antag_panel() + var/list/parts = list() + parts += ..() + parts += "Targets currently assigned by living hearts (Can give a false negative if they stole someone elses living heart):" + if(!sac_targetted.len) + parts += "None." + else + parts += sac_targetted.Join(",") + parts += "Targets actually sacrificed:" + if(!actually_sacced.len) + parts += "None." + else + parts += actually_sacced.Join(",") + + return (parts.Join("
    ") + "
    ") + + //////////////// // Objectives // //////////////// diff --git a/code/modules/antagonists/eldritch_cult/eldritch_effects.dm b/code/modules/antagonists/eldritch_cult/eldritch_effects.dm index 99f0ae7596..ad39e549eb 100644 --- a/code/modules/antagonists/eldritch_cult/eldritch_effects.dm +++ b/code/modules/antagonists/eldritch_cult/eldritch_effects.dm @@ -254,6 +254,7 @@ to_chat(human_user,"Your brain hurts when you look at this!") human_user.adjustOrganLoss(ORGAN_SLOT_BRAIN,20,190) SEND_SIGNAL(human_user, COMSIG_ADD_MOOD_EVENT, "gates_of_mansus", /datum/mood_event/gates_of_mansus) + log_game("[key_name(user)] stared at a pierced reality at [AREACOORD(user)]") /obj/effect/reality_smash name = "/improper reality smash" diff --git a/code/modules/antagonists/eldritch_cult/eldritch_items.dm b/code/modules/antagonists/eldritch_cult/eldritch_items.dm index da2c61ad16..ea6d1d50e2 100644 --- a/code/modules/antagonists/eldritch_cult/eldritch_items.dm +++ b/code/modules/antagonists/eldritch_cult/eldritch_items.dm @@ -6,6 +6,17 @@ w_class = WEIGHT_CLASS_SMALL ///Target var/mob/living/carbon/human/target + var/datum/antagonist/heretic/sac_targetter //The heretic who used this to acquire the current target - gets cleared when target gets sacrificed. + +/obj/item/living_heart/Initialize() + . = ..() + GLOB.living_heart_cache.Add(src) //Add is better than +=. + +/obj/item/living_heart/Destroy() + GLOB.living_heart_cache.Remove(src) + if(sac_targetter && target) + sac_targetter.sac_targetted.Remove(target.real_name) + return ..() /obj/item/living_heart/attack_self(mob/user) . = ..() @@ -39,7 +50,8 @@ background_icon_state = "bg_ecult" button_icon_state = "shatter" icon_icon = 'icons/mob/actions/actions_ecult.dmi' - check_flags = MOBILITY_HOLD|MOBILITY_MOVE|MOBILITY_USE + check_flags = NONE // required_mobility_flags handles this + required_mobility_flags = MOBILITY_HOLD|MOBILITY_MOVE|MOBILITY_USE var/mob/living/carbon/human/holder var/obj/item/melee/sickly_blade/sword @@ -51,11 +63,12 @@ /datum/action/innate/heretic_shatter/IsAvailable() if(IS_HERETIC(holder) || IS_HERETIC_MONSTER(holder)) - return TRUE + return ..() else return FALSE /datum/action/innate/heretic_shatter/Activate() + . = ..() var/turf/safe_turf = find_safe_turf(zlevels = sword.z, extended_safety_checks = TRUE) do_teleport(holder,safe_turf,forceMove = TRUE) to_chat(holder,"You feel a gust of energy flow through your body... the Rusted Hills heard your call...") diff --git a/code/modules/antagonists/eldritch_cult/eldritch_knowledge.dm b/code/modules/antagonists/eldritch_cult/eldritch_knowledge.dm index e0189944e5..481ac08ea5 100644 --- a/code/modules/antagonists/eldritch_cult/eldritch_knowledge.dm +++ b/code/modules/antagonists/eldritch_cult/eldritch_knowledge.dm @@ -1,6 +1,6 @@ /** - * #Eldritch Knwoledge + * #Eldritch Knowledge * * Datum that makes eldritch cultist interesting. * @@ -252,6 +252,10 @@ LH.target = null var/datum/antagonist/heretic/EC = carbon_user.mind.has_antag_datum(/datum/antagonist/heretic) + EC.actually_sacced.Add(H.real_name) + if(LH.sac_targetter) + LH.sac_targetter.sac_targetted.Remove(H.real_name) + LH.sac_targetter = null EC.total_sacrifices++ for(var/X in carbon_user.get_all_gear()) if(!istype(X,/obj/item/forbidden_book)) @@ -265,8 +269,14 @@ var/datum/objective/A = new A.owner = user.mind var/list/targets = list() + var/list/target_blacklist = list() + for(var/obj/item/living_heart/CLH in GLOB.living_heart_cache) + if(!CLH || !CLH.target || !CLH.target.mind) + continue + target_blacklist.Add(CLH.target.mind) + for(var/i in 0 to 3) - var/datum/mind/targeted = A.find_target()//easy way, i dont feel like copy pasting that entire block of code + var/datum/mind/targeted = A.find_target(blacklist = target_blacklist)//easy way, i dont feel like copy pasting that entire block of code if(!targeted) break targets[targeted.current.real_name] = targeted.current @@ -274,9 +284,24 @@ if(!LH.target && targets.len) LH.target = pick(targets) //Tsk tsk, you can and will get another target if you want it or not. + + if(LH.target) + target_blacklist = list() + for(var/obj/item/living_heart/CLH in (GLOB.living_heart_cache - LH)) //Recreate blacklist, excluding ourselves. + if(!CLH || !CLH.target || !CLH.target.mind) + continue + target_blacklist.Add(CLH.target.mind) + if(LH.target.mind in target_blacklist) //Someone was faster, or you tried to cheese the system. + to_chat(user, "It seems you were too slow, and your target of choice has already been selected by another living heart!") + LH.target = null + qdel(A) if(LH.target) to_chat(user,"Your new target has been selected, go and sacrifice [LH.target.real_name]!") + var/datum/antagonist/heretic/EC = carbon_user.mind.has_antag_datum(/datum/antagonist/heretic) + LH.sac_targetter = EC + EC.sac_targetted.Add(LH.target.real_name) + else to_chat(user,"target could not be found for living heart.") diff --git a/code/modules/antagonists/eldritch_cult/eldritch_magic.dm b/code/modules/antagonists/eldritch_cult/eldritch_magic.dm index ba79cca07a..bb95a8bdf3 100644 --- a/code/modules/antagonists/eldritch_cult/eldritch_magic.dm +++ b/code/modules/antagonists/eldritch_cult/eldritch_magic.dm @@ -566,7 +566,7 @@ human_user.adjustBruteLoss(-10, FALSE) human_user.adjustFireLoss(-10, FALSE) human_user.adjustStaminaLoss(-10, FALSE) - human_user.adjustToxLoss(-10, FALSE) + human_user.adjustToxLoss(-10, FALSE, TRUE) human_user.adjustOxyLoss(-10) /obj/effect/proc_holder/spell/pointed/manse_link diff --git a/code/modules/antagonists/eldritch_cult/knowledge/ash_lore.dm b/code/modules/antagonists/eldritch_cult/knowledge/ash_lore.dm index 1edb0ff19a..48b2a6b225 100644 --- a/code/modules/antagonists/eldritch_cult/knowledge/ash_lore.dm +++ b/code/modules/antagonists/eldritch_cult/knowledge/ash_lore.dm @@ -193,7 +193,7 @@ var/list/trait_list = list(TRAIT_NOBREATH,TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE) /datum/eldritch_knowledge/final/ash_final/on_finished_recipe(mob/living/user, list/atoms, loc) - priority_announce("$^@&#*$^@(#&$(@&#^$&#^@# Fear the blaze, for Ashbringer [user.real_name] has come! $^@&#*$^@(#&$(@&#^$&#^@#","#$^@&#*$^@(#&$(@&#^$&#^@#", 'sound/announcer/classic/spanomalies.ogg') + priority_announce("$^@&#*$^@(#&$(@&#^$&#^@# Fear the blaze, for the Ashlord, [user.real_name] has ascended! The flames shall consume all! $^@&#*$^@(#&$(@&#^$&#^@#","#$^@&#*$^@(#&$(@&#^$&#^@#", 'sound/announcer/classic/spanomalies.ogg') user.mind.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/fire_cascade/big) user.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/fire_sworn) var/mob/living/carbon/human/H = user @@ -201,6 +201,7 @@ H.physiology.burn_mod *= 0.5 var/datum/antagonist/heretic/ascension = H.mind.has_antag_datum(/datum/antagonist/heretic) ascension.ascended = TRUE + H.client?.give_award(/datum/award/achievement/misc/ash_ascension, H) for(var/X in trait_list) ADD_TRAIT(user,X,MAGIC_TRAIT) return ..() diff --git a/code/modules/antagonists/eldritch_cult/knowledge/flesh_lore.dm b/code/modules/antagonists/eldritch_cult/knowledge/flesh_lore.dm index 2b9f5b309f..024fddbca8 100644 --- a/code/modules/antagonists/eldritch_cult/knowledge/flesh_lore.dm +++ b/code/modules/antagonists/eldritch_cult/knowledge/flesh_lore.dm @@ -232,7 +232,7 @@ log_game("[key_name_admin(ghost_candidate)] has taken control of ([key_name_admin(summoned)]).") summoned.ghostize(FALSE) summoned.key = ghost_candidate.key - summoned.mind.add_antag_datum(/datum/antagonist/heretic_monster) + summoned.mind.add_antag_datum(/datum/antagonist/heretic_monster) //no you will NOT get the achivement you ghost. var/datum/antagonist/heretic_monster/monster = summoned.mind.has_antag_datum(/datum/antagonist/heretic_monster) var/datum/antagonist/heretic/master = user.mind.has_antag_datum(/datum/antagonist/heretic) monster.set_owner(master) @@ -243,11 +243,14 @@ user.SetImmobilized(0) priority_announce("$^@&#*$^@(#&$(@&#^$&#^@# Fear the dark, for king of arms has ascended! Lord of the night has come! $^@&#*$^@(#&$(@&#^$&#^@#","#$^@&#*$^@(#&$(@&#^$&#^@#", 'sound/announcer/classic/spanomalies.ogg') log_game("[user.real_name] ascended as [summoned.real_name]") - var/mob/living/carbon/carbon_user = user - var/datum/antagonist/heretic/ascension = carbon_user.mind.has_antag_datum(/datum/antagonist/heretic) + if(!ishuman(user)) + return + var/mob/living/carbon/human/H = user + H.client?.give_award(/datum/award/achievement/misc/flesh_ascension, H) + var/datum/antagonist/heretic/ascension = user.mind.has_antag_datum(/datum/antagonist/heretic) ascension.ascended = TRUE - carbon_user.mind.transfer_to(summoned, TRUE) - carbon_user.gib() + user.mind.transfer_to(summoned, TRUE) + user.gib() return ..() diff --git a/code/modules/antagonists/eldritch_cult/knowledge/rust_lore.dm b/code/modules/antagonists/eldritch_cult/knowledge/rust_lore.dm index 9d65396c63..5dc42855e5 100644 --- a/code/modules/antagonists/eldritch_cult/knowledge/rust_lore.dm +++ b/code/modules/antagonists/eldritch_cult/knowledge/rust_lore.dm @@ -169,7 +169,8 @@ var/mob/living/carbon/human/H = user H.physiology.brute_mod *= 0.5 H.physiology.burn_mod *= 0.5 - priority_announce("$^@&#*$^@(#&$(@&#^$&#^@# Fear the decay, for Rustbringer [user.real_name] has come! $^@&#*$^@(#&$(@&#^$&#^@#","#$^@&#*$^@(#&$(@&#^$&#^@#", 'sound/announcer/classic/spanomalies.ogg') + H.client?.give_award(/datum/award/achievement/misc/rust_ascension, H) + priority_announce("$^@&#*$^@(#&$(@&#^$&#^@# Fear the decay, for the Rustbringer, [user.real_name] has ascended! None shall escape the corrosion! $^@&#*$^@(#&$(@&#^$&#^@#","#$^@&#*$^@(#&$(@&#^$&#^@#", 'sound/announcer/classic/spanomalies.ogg') new /datum/rust_spread(loc) var/datum/antagonist/heretic/ascension = H.mind.has_antag_datum(/datum/antagonist/heretic) ascension.ascended = TRUE @@ -183,7 +184,7 @@ var/mob/living/carbon/human/human_user = user human_user.adjustBruteLoss(-6, FALSE) human_user.adjustFireLoss(-6, FALSE) - human_user.adjustToxLoss(-6, FALSE) + human_user.adjustToxLoss(-6, FALSE, TRUE) human_user.adjustOxyLoss(-6, FALSE) human_user.adjustStaminaLoss(-20) diff --git a/code/modules/antagonists/morph/morph.dm b/code/modules/antagonists/morph/morph.dm index 5844aad02b..077c380ef0 100644 --- a/code/modules/antagonists/morph/morph.dm +++ b/code/modules/antagonists/morph/morph.dm @@ -15,7 +15,6 @@ stop_automated_movement = 1 status_flags = CANPUSH pass_flags = PASSTABLE - ventcrawler = VENTCRAWLER_ALWAYS atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) minbodytemp = 0 maxHealth = 150 @@ -53,6 +52,10 @@ You can attack any item or dead creature to consume it - creatures will fully restore your health. \ Finally, you can restore yourself to your original form while morphed by shift-clicking yourself.
    " +/mob/living/simple_animal/hostile/morph/Initialize() + . = ..() + src.AddElement(/datum/element/ventcrawling, given_tier = VENTCRAWLER_ALWAYS) + /mob/living/simple_animal/hostile/morph/examine(mob/user) if(morphed) . = form.examine(user) diff --git a/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm b/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm index bf6599d782..7d669e5c41 100644 --- a/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm +++ b/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm @@ -104,7 +104,7 @@ switch(deconstruction_state) if(NUKESTATE_INTACT) - if(istype(I, /obj/item/screwdriver/nuke)) + if(istype(I, /obj/item/screwdriver/nuke)) //Special case, cannot replace with tool_behavior to_chat(user, "You start removing [src]'s front panel's screws...") if(I.use_tool(src, user, 60, volume=100)) deconstruction_state = NUKESTATE_UNSCREWED diff --git a/code/modules/antagonists/revenant/revenant.dm b/code/modules/antagonists/revenant/revenant.dm index f5ebcffe35..ef58e5af39 100644 --- a/code/modules/antagonists/revenant/revenant.dm +++ b/code/modules/antagonists/revenant/revenant.dm @@ -72,6 +72,7 @@ var/list/drained_mobs = list() //Cannot harvest the same mob twice var/perfectsouls = 0 //How many perfect, regen-cap increasing souls the revenant has. //TODO, add objective for getting a perfect soul(s?) var/generated_objectives_and_spells = FALSE + var/telekinesis_cooldown /mob/living/simple_animal/revenant/Initialize(mapload) . = ..() @@ -93,13 +94,16 @@ /mob/living/simple_animal/revenant/Login() ..() - to_chat(src, "You are a revenant.") - to_chat(src, "Your formerly mundane spirit has been infused with alien energies and empowered into a revenant.") - to_chat(src, "You are not dead, not alive, but somewhere in between. You are capable of limited interaction with both worlds.") - to_chat(src, "You are invincible and invisible to everyone but other ghosts. Most abilities will reveal you, rendering you vulnerable.") - to_chat(src, "To function, you are to drain the life essence from humans. This essence is a resource, as well as your health, and will power all of your abilities.") - to_chat(src, "You do not remember anything of your past lives, nor will you remember anything about this one after your death.") - to_chat(src, "Be sure to read the wiki page to learn more.") + var/revenant_greet + revenant_greet += "You are a revenant." + revenant_greet += "Your formerly mundane spirit has been infused with alien energies and empowered into a revenant." + revenant_greet += "You are not dead, not alive, but somewhere in between. You are capable of limited interaction with both worlds." + revenant_greet += "You are invincible and invisible to everyone but other ghosts. Most abilities will reveal you, rendering you vulnerable." + revenant_greet += "To function, you are to drain the life essence from humans. This essence is a resource, as well as your health, and will power all of your abilities." + revenant_greet += "You do not remember anything of your past lives, nor will you remember anything about this one after your death." + revenant_greet += "Be sure to read the wiki page to learn more." + revenant_greet += "You are also able to telekinetically throw objects by clickdragging them." + to_chat(src, revenant_greet) if(!generated_objectives_and_spells) generated_objectives_and_spells = TRUE mind.assigned_role = ROLE_REVENANT @@ -317,6 +321,12 @@ to_chat(src, "Lost [essence_amt]E[source ? " from [source]":""].") return 1 +/mob/living/simple_animal/revenant/proc/telekinesis_cooldown_end() + if(!telekinesis_cooldown) + CRASH("telekinesis_cooldown_end ran when telekinesis_cooldown on [src] was false") + else + telekinesis_cooldown = FALSE + /mob/living/simple_animal/revenant/proc/death_reset() revealed = FALSE unreveal_time = 0 @@ -431,6 +441,38 @@ qdel(revenant) ..() +/proc/RevenantThrow(over, mob/user, obj/item/throwable) + var/mob/living/simple_animal/revenant/spooker = user + if(!istype(throwable)) + return + if(!throwable.anchored && !spooker.telekinesis_cooldown && spooker.essence > 20) + if(7 < get_dist(throwable, spooker)) + return + if(3 >= get_dist(throwable, spooker)) + spooker.stun(10) + spooker.reveal(25) + else + spooker.stun(20) + spooker.reveal(50) + spooker.change_essence_amount(-20, FALSE, "telekinesis") + spooker.telekinesis_cooldown = TRUE + throwable.float(TRUE, TRUE) + sleep(20) + throwable.DoRevenantThrowEffects(over) + throwable.throw_at(over, 10, 2) + ADD_TRAIT(throwable, TRAIT_SPOOKY_THROW, "revenant") + log_combat(throwable, over, "spooky telekinesised at", throwable) + var/obj/effect/temp_visual/telekinesis/T = new(get_turf(throwable)) + T.color = "#8715b4" + addtimer(CALLBACK(spooker, /mob/living/simple_animal/revenant.proc/telekinesis_cooldown_end), 50) + sleep(5) + throwable.float(FALSE, TRUE) + + +//Use this for effects you want to happen when a revenant throws stuff, check the TRAIT_SPOOKY_THROW if you want to know if its still being thrown +/obj/item/proc/DoRevenantThrowEffects(atom/target) + return TRUE + //objectives /datum/objective/revenant var/targetAmount = 100 diff --git a/code/modules/antagonists/swarmer/swarmer.dm b/code/modules/antagonists/swarmer/swarmer.dm index 7444905b89..f283e33899 100644 --- a/code/modules/antagonists/swarmer/swarmer.dm +++ b/code/modules/antagonists/swarmer/swarmer.dm @@ -43,7 +43,7 @@ to_chat(user, "Picking up the swarmer may cause it to activate. You should be careful about this.") /obj/effect/mob_spawn/swarmer/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/screwdriver) && user.a_intent != INTENT_HARM) + if(W.tool_behaviour == TOOL_SCREWDRIVER && user.a_intent != INTENT_HARM) user.visible_message("[usr.name] deactivates [src].", "After some fiddling, you find a way to disable [src]'s power source.", "You hear clicking.") @@ -93,7 +93,6 @@ AIStatus = AI_OFF pass_flags = PASSTABLE mob_size = MOB_SIZE_TINY - ventcrawler = VENTCRAWLER_ALWAYS ranged = 1 projectiletype = /obj/item/projectile/beam/disabler ranged_cooldown_time = 20 @@ -112,6 +111,7 @@ remove_verb(src, /mob/living/verb/pulled) for(var/datum/atom_hud/data/diagnostic/diag_hud in GLOB.huds) diag_hud.add_to_hud(src) + AddElement(/datum/element/ventcrawling, given_tier = VENTCRAWLER_ALWAYS) /mob/living/simple_animal/hostile/swarmer/med_hud_set_health() var/image/holder = hud_list[DIAG_HUD] diff --git a/code/modules/antagonists/traitor/classes/assassin.dm b/code/modules/antagonists/traitor/classes/assassin.dm index 6b709aac59..8175ad9736 100644 --- a/code/modules/antagonists/traitor/classes/assassin.dm +++ b/code/modules/antagonists/traitor/classes/assassin.dm @@ -8,11 +8,9 @@ /datum/traitor_class/human/assassin/forge_single_objective(datum/antagonist/traitor/T) .=1 var/permakill_prob = 20 - var/is_dynamic = FALSE var/datum/game_mode/dynamic/mode if(istype(SSticker.mode,/datum/game_mode/dynamic)) mode = SSticker.mode - is_dynamic = TRUE permakill_prob = max(0,mode.threat_level-50) var/list/active_ais = active_ais() if(active_ais.len && prob(100/GLOB.joined_player_list.len)) @@ -20,11 +18,6 @@ destroy_objective.owner = T.owner destroy_objective.find_target() T.add_objective(destroy_objective) - else if(prob(30) || (is_dynamic && (mode.storyteller.flags & NO_ASSASSIN))) - var/datum/objective/maroon/maroon_objective = new - maroon_objective.owner = T.owner - maroon_objective.find_target() - T.add_objective(maroon_objective) else if(prob(permakill_prob)) var/datum/objective/assassinate/kill_objective = new kill_objective.owner = T.owner diff --git a/code/modules/antagonists/traitor/classes/human.dm b/code/modules/antagonists/traitor/classes/human.dm index 30aa11e39d..16f65a63c3 100644 --- a/code/modules/antagonists/traitor/classes/human.dm +++ b/code/modules/antagonists/traitor/classes/human.dm @@ -41,11 +41,6 @@ destroy_objective.owner = T.owner destroy_objective.find_target() T.add_objective(destroy_objective) - else if(prob(30) || (is_dynamic && (mode.storyteller.flags & NO_ASSASSIN))) - var/datum/objective/maroon/maroon_objective = new - maroon_objective.owner = T.owner - maroon_objective.find_target() - T.add_objective(maroon_objective) else if(prob(max(0,assassin_prob-20))) var/datum/objective/assassinate/kill_objective = new kill_objective.owner = T.owner diff --git a/code/modules/antagonists/traitor/classes/subterfuge.dm b/code/modules/antagonists/traitor/classes/subterfuge.dm index 07707b69e1..73dc455a45 100644 --- a/code/modules/antagonists/traitor/classes/subterfuge.dm +++ b/code/modules/antagonists/traitor/classes/subterfuge.dm @@ -12,16 +12,10 @@ mode = SSticker.mode assassin_prob = max(0,mode.threat_level-40) if(prob(assassin_prob)) - if(prob(assassin_prob)) - var/datum/objective/assassinate/once/kill_objective = new - kill_objective.owner = T.owner - kill_objective.find_target() - T.add_objective(kill_objective) - else - var/datum/objective/maroon/maroon_objective = new - maroon_objective.owner = T.owner - maroon_objective.find_target() - T.add_objective(maroon_objective) + var/datum/objective/assassinate/once/kill_objective = new + kill_objective.owner = T.owner + kill_objective.find_target() + T.add_objective(kill_objective) else var/list/weights = list() weights["sabo"] = length(subtypesof(/datum/sabotage_objective)) diff --git a/code/modules/antagonists/traitor/equipment/contractor.dm b/code/modules/antagonists/traitor/equipment/contractor.dm index 28298f4983..525f40d1f7 100644 --- a/code/modules/antagonists/traitor/equipment/contractor.dm +++ b/code/modules/antagonists/traitor/equipment/contractor.dm @@ -43,7 +43,7 @@ var/list/to_generate = list( CONTRACT_PAYOUT_LARGE, CONTRACT_PAYOUT_MEDIUM, - CONTRACT_PAYOUT_SMALL, + CONTRACT_PAYOUT_MEDIUM, CONTRACT_PAYOUT_SMALL, CONTRACT_PAYOUT_SMALL, CONTRACT_PAYOUT_SMALL diff --git a/code/modules/arousal/arousal.dm b/code/modules/arousal/arousal.dm index 99683f1688..2f7f701012 100644 --- a/code/modules/arousal/arousal.dm +++ b/code/modules/arousal/arousal.dm @@ -9,6 +9,7 @@ var/hidden_underwear = FALSE var/hidden_undershirt = FALSE var/hidden_socks = FALSE + var/arousal_rate = 1 //Mob procs /mob/living/carbon/human/verb/underwear_toggle() @@ -20,29 +21,34 @@ return if(confirm == "Top") hidden_undershirt = !hidden_undershirt + log_message("[hidden_undershirt ? "removed" : "put on" ] [p_their()] undershirt.", LOG_EMOTE) if(confirm == "Bottom") hidden_underwear = !hidden_underwear + log_message("[hidden_underwear ? "removed" : "put on"] [p_their()] underwear.", LOG_EMOTE) if(confirm == "Socks") hidden_socks = !hidden_socks + log_message("[hidden_socks ? "removed" : "put on"] [p_their()] socks.", LOG_EMOTE) if(confirm == "All") var/on_off = (hidden_undershirt || hidden_underwear || hidden_socks) ? FALSE : TRUE hidden_undershirt = on_off hidden_underwear = on_off hidden_socks = on_off + log_message("[on_off ? "removed" : "put on"] all [p_their()] undergarments.", LOG_EMOTE) update_body(TRUE) -/mob/living/carbon/human/proc/adjust_arousal(strength,aphro = FALSE,maso = FALSE) // returns all genitals that were adjust +/mob/living/carbon/human/proc/adjust_arousal(strength, cause = "manual toggle", aphro = FALSE,maso = FALSE) // returns all genitals that were adjust var/list/obj/item/organ/genital/genit_list = list() if(!client?.prefs.arousable || (aphro && (client?.prefs.cit_toggles & NO_APHRO)) || (maso && !HAS_TRAIT(src, TRAIT_MASO))) return // no adjusting made here + var/enabling = strength > 0 for(var/obj/item/organ/genital/G in internal_organs) - if(G.genital_flags & GENITAL_CAN_AROUSE && !G.aroused_state && prob(strength*G.sensitivity)) - G.set_aroused_state(strength > 0) + if(G.genital_flags & GENITAL_CAN_AROUSE && !G.aroused_state && prob(abs(strength)*G.sensitivity * arousal_rate)) + G.set_aroused_state(enabling,cause) G.update_appearance() if(G.aroused_state) genit_list += G @@ -64,6 +70,7 @@ return var/turfing = isturf(target) G.generate_fluid(R) + log_message("Climaxed using [G] with [target]", LOG_EMOTE) if(spill && R.total_volume >= 5) R.reaction(turfing ? target : target.loc, TOUCH, 1, 0) if(!turfing) @@ -189,7 +196,7 @@ return TRUE //Here's the main proc itself -/mob/living/carbon/human/proc/mob_climax(forced_climax=FALSE) //Forced is instead of the other proc, makes you cum if you have the tools for it, ignoring restraints +/mob/living/carbon/human/proc/mob_climax(forced_climax=FALSE,cause = "") //Forced is instead of the other proc, makes you cum if you have the tools for it, ignoring restraints if(mb_cd_timer > world.time) if(!forced_climax) //Don't spam the message to the victim if forced to come too fast to_chat(src, "You need to wait [DisplayTimeText((mb_cd_timer - world.time), TRUE)] before you can do that again!") @@ -202,6 +209,7 @@ to_chat(src, "You can't do that while dead!") return if(forced_climax) //Something forced us to cum, this is not a masturbation thing and does not progress to the other checks + log_message("was forced to climax by [cause]",LOG_EMOTE) for(var/obj/item/organ/genital/G in internal_organs) if(!CHECK_BITFIELD(G.genital_flags, CAN_CLIMAX_WITH)) //Skip things like wombs and testicles continue @@ -272,7 +280,6 @@ var/obj/item/reagent_containers/fluid_container = pick_climax_container() if(fluid_container && available_rosie_palms(TRUE, /obj/item/reagent_containers)) mob_fill_container(picked_organ, fluid_container) - mb_cd_timer = world.time + mb_cd_length /mob/living/carbon/human/verb/climax_verb() diff --git a/code/modules/arousal/genitals.dm b/code/modules/arousal/genitals.dm index 67de745d22..723049a784 100644 --- a/code/modules/arousal/genitals.dm +++ b/code/modules/arousal/genitals.dm @@ -27,11 +27,12 @@ if(do_update) update() -/obj/item/organ/genital/proc/set_aroused_state(new_state) +/obj/item/organ/genital/proc/set_aroused_state(new_state,cause = "manual toggle") if(!(genital_flags & GENITAL_CAN_AROUSE)) return FALSE if(!((HAS_TRAIT(owner,TRAIT_PERMABONER) && !new_state) || HAS_TRAIT(owner,TRAIT_NEVERBONER) && new_state)) aroused_state = new_state + owner.log_message("[src]'s arousal was [new_state ? "enabled" : "disabled"] due to [cause]", LOG_EMOTE) return aroused_state /obj/item/organ/genital/proc/update() @@ -76,11 +77,19 @@ if(GEN_VISIBLE_ALWAYS) genital_flags |= GENITAL_THROUGH_CLOTHES if(owner) + owner.log_message("Exposed their [src]",LOG_EMOTE) owner.exposed_genitals += src + if(GEN_VISIBLE_NO_CLOTHES) + if(owner) + owner.log_message("Hid their [src] under clothes only",LOG_EMOTE) if(GEN_VISIBLE_NO_UNDIES) genital_flags |= GENITAL_UNDIES_HIDDEN + if(owner) + owner.log_message("Hid their [src] under underwear",LOG_EMOTE) if(GEN_VISIBLE_NEVER) genital_flags |= GENITAL_HIDDEN + if(owner) + owner.log_message("Hid their [src] completely",LOG_EMOTE) if(update && owner && ishuman(owner)) //recast to use update genitals proc var/mob/living/carbon/human/H = owner diff --git a/code/modules/arousal/toys/dildos.dm b/code/modules/arousal/toys/dildos.dm index 0cfa57e5fc..2482b93300 100644 --- a/code/modules/arousal/toys/dildos.dm +++ b/code/modules/arousal/toys/dildos.dm @@ -103,29 +103,29 @@ name = "knotted dildo" attack_verb = list("penetrated", "knotted", "slapped", "inseminated") -obj/item/dildo/human +/obj/item/dildo/human dildo_shape = "human" name = "human dildo" attack_verb = list("penetrated", "slapped", "inseminated") -obj/item/dildo/plain +/obj/item/dildo/plain dildo_shape = "plain" name = "plain dildo" attack_verb = list("penetrated", "slapped", "inseminated") -obj/item/dildo/flared +/obj/item/dildo/flared dildo_shape = "flared" name = "flared dildo" attack_verb = list("penetrated", "slapped", "neighed", "gaped", "prolapsed", "inseminated") -obj/item/dildo/flared/huge +/obj/item/dildo/flared/huge name = "literal horse cock" desc = "THIS THING IS HUGE!" dildo_size = 4 force = 10 hitsound = 'sound/weapons/klonk.ogg' -obj/item/dildo/custom +/obj/item/dildo/custom name = "customizable dildo" desc = "Thanks to significant advances in synthetic nanomaterials, this dildo is capable of taking on many different forms to fit the user's preferences! Pricy!" can_customize = TRUE @@ -136,9 +136,9 @@ obj/item/dildo/custom // Suicide acts, by request /obj/item/dildo/proc/manual_suicide(mob/living/user) - user.visible_message("[user] finally finishes deepthroating the [src], and their life.") - user.adjustOxyLoss(200) - user.death(0) + user.visible_message("[user] finally finishes deepthroating the [src], and their life.") + user.adjustOxyLoss(200) + user.death(0) /obj/item/dildo/suicide_act(mob/living/user) // is_knotted = ((src.dildo_shape == "knotted")?"They swallowed the knot":"Their face is turning blue") diff --git a/code/modules/assembly/flash.dm b/code/modules/assembly/flash.dm index 8a0645f311..07a9f499f8 100644 --- a/code/modules/assembly/flash.dm +++ b/code/modules/assembly/flash.dm @@ -30,6 +30,9 @@ attack(user,user) return FIRELOSS +/obj/item/assembly/flash/DoRevenantThrowEffects(atom/target) + AOE_flash() + /obj/item/assembly/flash/update_icon(flash = FALSE) cut_overlays() attached_overlays = list() diff --git a/code/modules/assembly/holder.dm b/code/modules/assembly/holder.dm index bf56449a0c..84e70bbbb3 100644 --- a/code/modules/assembly/holder.dm +++ b/code/modules/assembly/holder.dm @@ -62,6 +62,7 @@ master.update_icon() /obj/item/assembly_holder/Crossed(atom/movable/AM as mob|obj) + . = ..() if(a_left) a_left.Crossed(AM) if(a_right) diff --git a/code/modules/asset_cache/asset_cache_client.dm b/code/modules/asset_cache/asset_cache_client.dm index f462fe386b..3cff8cb41f 100644 --- a/code/modules/asset_cache/asset_cache_client.dm +++ b/code/modules/asset_cache/asset_cache_client.dm @@ -1,5 +1,5 @@ -/// Process asset cache client topic calls for "asset_cache_confirm_arrival=[INT]" +/// Process asset cache client topic calls for `"asset_cache_confirm_arrival=[INT]"` /client/proc/asset_cache_confirm_arrival(job_id) var/asset_cache_job = round(text2num(job_id)) //because we skip the limiter, we have to make sure this is a valid arrival and not somebody tricking us into letting them append to a list without limit. @@ -10,7 +10,7 @@ return asset_cache_job || TRUE -/// Process asset cache client topic calls for "asset_cache_preload_data=[HTML+JSON_STRING] +/// Process asset cache client topic calls for `"asset_cache_preload_data=[HTML+JSON_STRING]"` /client/proc/asset_cache_preload_data(data) var/json = data var/list/preloaded_assets = json_decode(json) diff --git a/code/modules/asset_cache/asset_cache_item.dm b/code/modules/asset_cache/asset_cache_item.dm index 72d976bf11..059ebaebca 100644 --- a/code/modules/asset_cache/asset_cache_item.dm +++ b/code/modules/asset_cache/asset_cache_item.dm @@ -24,7 +24,7 @@ /datum/asset_cache_item/New(name, file) if (!isfile(file)) file = fcopy_rsc(file) - + hash = md5asfile(file) //icons sent to the rsc sometimes md5 incorrectly if (!hash) CRASH("invalid asset sent to asset cache") diff --git a/code/modules/asset_cache/asset_list_items.dm b/code/modules/asset_cache/asset_list_items.dm index 3b6a30a02a..e32fcee639 100644 --- a/code/modules/asset_cache/asset_list_items.dm +++ b/code/modules/asset_cache/asset_list_items.dm @@ -3,7 +3,7 @@ /datum/asset/simple/tgui_common keep_local_name = TRUE assets = list( - "tgui-common.chunk.js" = 'tgui/public/tgui-common.chunk.js', + "tgui-common.bundle.js" = 'tgui/public/tgui-common.bundle.js', ) /datum/asset/simple/tgui @@ -48,7 +48,8 @@ "smmon_4.gif" = 'icons/program_icons/smmon_4.gif', "smmon_5.gif" = 'icons/program_icons/smmon_5.gif', "smmon_6.gif" = 'icons/program_icons/smmon_6.gif', - "borg_mon.gif" = 'icons/program_icons/borg_mon.gif' + "borg_mon.gif" = 'icons/program_icons/borg_mon.gif', + "robotact.gif" = 'icons/program_icons/robotact.gif' ) /datum/asset/simple/radar_assets @@ -156,7 +157,6 @@ ) /datum/asset/simple/namespaced/fontawesome - legacy = TRUE assets = list( "fa-regular-400.eot" = 'html/font-awesome/webfonts/fa-regular-400.eot', "fa-regular-400.woff" = 'html/font-awesome/webfonts/fa-regular-400.woff', @@ -172,7 +172,6 @@ /datum/asset/spritesheet/chat/register() InsertAll("emoji", 'icons/emoji.dmi') InsertAll("emoji", 'icons/emoji_32.dmi') - // pre-loading all lanugage icons also helps to avoid meta InsertAll("language", 'icons/misc/language.dmi') // catch languages which are pulling icons from another file @@ -190,7 +189,7 @@ ) /datum/asset/simple/namespaced/common - assets = list("padlock.png" = 'html/padlock.png') + assets = list("padlock.png" = 'html/padlock.png') parents = list("common.css" = 'html/browser/common.css') /datum/asset/simple/permissions @@ -222,7 +221,7 @@ "boss5.gif" = 'icons/UI_Icons/Arcade/boss5.gif', "boss6.gif" = 'icons/UI_Icons/Arcade/boss6.gif', ) -/* + /datum/asset/spritesheet/simple/achievements name ="achievements" assets = list( @@ -233,6 +232,7 @@ "bbgum" = 'icons/UI_Icons/Achievements/Boss/bbgum.png', "colossus" = 'icons/UI_Icons/Achievements/Boss/colossus.png', "hierophant" = 'icons/UI_Icons/Achievements/Boss/hierophant.png', + "drake" = 'icons/UI_Icons/Achievements/Boss/drake.png', "legion" = 'icons/UI_Icons/Achievements/Boss/legion.png', "miner" = 'icons/UI_Icons/Achievements/Boss/miner.png', "swarmer" = 'icons/UI_Icons/Achievements/Boss/swarmer.png', @@ -246,28 +246,23 @@ "clownking" = 'icons/UI_Icons/Achievements/Misc/clownking.png', "clownthanks" = 'icons/UI_Icons/Achievements/Misc/clownthanks.png', "rule8" = 'icons/UI_Icons/Achievements/Misc/rule8.png', + "longshift" = 'icons/UI_Icons/Achievements/Misc/longshift.png', "snail" = 'icons/UI_Icons/Achievements/Misc/snail.png', "ascension" = 'icons/UI_Icons/Achievements/Misc/ascension.png', + "ashascend" = 'icons/UI_Icons/Achievements/Misc/ashascend.png', + "fleshascend" = 'icons/UI_Icons/Achievements/Misc/fleshascend.png', + "rustascend" = 'icons/UI_Icons/Achievements/Misc/rustascend.png', + "voidascend" = 'icons/UI_Icons/Achievements/Misc/voidascend.png', + "toolbox_soul" = 'icons/UI_Icons/Achievements/Misc/toolbox_soul.png', + "chem_tut" = 'icons/UI_Icons/Achievements/Misc/chem_tut.png', "mining" = 'icons/UI_Icons/Achievements/Skills/mining.png', - "assistant" = 'icons/UI_Icons/Achievements/Mafia/assistant.png', - "changeling" = 'icons/UI_Icons/Achievements/Mafia/changeling.png', - "chaplain" = 'icons/UI_Icons/Achievements/Mafia/chaplain.png', - "clown" = 'icons/UI_Icons/Achievements/Mafia/clown.png', - "detective" = 'icons/UI_Icons/Achievements/Mafia/detective.png', - "fugitive" = 'icons/UI_Icons/Achievements/Mafia/fugitive.png', + "mafia" = 'icons/UI_Icons/Achievements/Mafia/mafia.png', + "town" = 'icons/UI_Icons/Achievements/Mafia/town.png', + "neutral" = 'icons/UI_Icons/Achievements/Mafia/neutral.png', "hated" = 'icons/UI_Icons/Achievements/Mafia/hated.png', - "hop" = 'icons/UI_Icons/Achievements/Mafia/hop.png', - "lawyer" = 'icons/UI_Icons/Achievements/Mafia/lawyer.png', - "md" = 'icons/UI_Icons/Achievements/Mafia/md.png', - "nightmare" = 'icons/UI_Icons/Achievements/Mafia/nightmare.png', - "obsessed" = 'icons/UI_Icons/Achievements/Mafia/obsessed.png', - "psychologist" = 'icons/UI_Icons/Achievements/Mafia/psychologist.png', - "thoughtfeeder" = 'icons/UI_Icons/Achievements/Mafia/thoughtfeeder.png', - "traitor" = 'icons/UI_Icons/Achievements/Mafia/traitor.png', "basemafia" ='icons/UI_Icons/Achievements/basemafia.png', "frenching" = 'icons/UI_Icons/Achievements/Misc/frenchingthebubble.png' ) -*/ /datum/asset/spritesheet/simple/minesweeper name = "minesweeper" @@ -288,7 +283,7 @@ ) /datum/asset/spritesheet/simple/pills - name ="pills" + name = "pills" assets = list( "pill1" = 'icons/UI_Icons/Pills/pill1.png', "pill2" = 'icons/UI_Icons/Pills/pill2.png', @@ -314,6 +309,27 @@ "pill22" = 'icons/UI_Icons/Pills/pill22.png', ) +// /datum/asset/spritesheet/simple/condiments +// name = "condiments" +// assets = list( +// CONDIMASTER_STYLE_FALLBACK = 'icons/UI_Icons/Condiments/emptycondiment.png', +// "enzyme" = 'icons/UI_Icons/Condiments/enzyme.png', +// "flour" = 'icons/UI_Icons/Condiments/flour.png', +// "mayonnaise" = 'icons/UI_Icons/Condiments/mayonnaise.png', +// "milk" = 'icons/UI_Icons/Condiments/milk.png', +// "blackpepper" = 'icons/UI_Icons/Condiments/peppermillsmall.png', +// "rice" = 'icons/UI_Icons/Condiments/rice.png', +// "sodiumchloride" = 'icons/UI_Icons/Condiments/saltshakersmall.png', +// "soymilk" = 'icons/UI_Icons/Condiments/soymilk.png', +// "soysauce" = 'icons/UI_Icons/Condiments/soysauce.png', +// "sugar" = 'icons/UI_Icons/Condiments/sugar.png', +// "ketchup" = 'icons/UI_Icons/Condiments/ketchup.png', +// "capsaicin" = 'icons/UI_Icons/Condiments/hotsauce.png', +// "frostoil" = 'icons/UI_Icons/Condiments/coldsauce.png', +// "bbqsauce" = 'icons/UI_Icons/Condiments/bbqsauce.png', +// "cornoil" = 'icons/UI_Icons/Condiments/oliveoil.png', +// ) + //this exists purely to avoid meta by pre-loading all language icons. /datum/asset/language/register() for(var/path in typesof(/datum/language)) @@ -460,7 +476,7 @@ /datum/asset/simple/orbit assets = list( - "ghost.png" = 'html/ghost.png' + "ghost.png" = 'html/ghost.png' ) /datum/asset/simple/vv @@ -484,3 +500,56 @@ /datum/asset/spritesheet/mafia/register() InsertAll("", 'icons/obj/mafia.dmi') ..() + +/datum/asset/simple/portraits + var/tab = "use subtypes of this please" + assets = list() + +/datum/asset/simple/portraits/New() + if(!SSpersistence.paintings || !SSpersistence.paintings[tab] || !length(SSpersistence.paintings[tab])) + return + for(var/p in SSpersistence.paintings[tab]) + var/list/portrait = p + var/png = "data/paintings/[tab]/[portrait["md5"]].png" + if(fexists(png)) + var/asset_name = "[tab]_[portrait["md5"]]" + assets[asset_name] = png + ..() //this is where it registers all these assets we added to the list + +/datum/asset/simple/portraits/library + tab = "library" + +/datum/asset/simple/portraits/library_secure + tab = "library_secure" + +/datum/asset/simple/portraits/library_private + tab = "library_private" + +/datum/asset/simple/safe + assets = list( + "safe_dial.png" = 'html/safe_dial.png' + ) + +// /datum/asset/spritesheet/fish +// name = "fish" + +// /datum/asset/spritesheet/fish/register() +// for (var/path in subtypesof(/datum/aquarium_behaviour/fish)) +// var/datum/aquarium_behaviour/fish/fish_type = path +// var/fish_icon = initial(fish_type.icon) +// var/fish_icon_state = initial(fish_type.icon_state) +// var/id = sanitize_css_class_name("[fish_icon][fish_icon_state]") +// if(sprites[id]) //no dupes +// continue +// Insert(id, fish_icon, fish_icon_state) +// ..() + +/// Removes all non-alphanumerics from the text, keep in mind this can lead to id conflicts +/proc/sanitize_css_class_name(name) + var/static/regex/regex = new(@"[^a-zA-Z0-9]","g") + return replacetext(name, regex, "") + +/datum/asset/simple/tutorial_advisors + assets = list( + "chem_help_advisor.gif" = 'icons/UI_Icons/Advisors/chem_help_advisor.gif', + ) diff --git a/code/modules/atmospherics/environmental/LINDA_turf_tile.dm b/code/modules/atmospherics/environmental/LINDA_turf_tile.dm index eee8859485..244b51bd2b 100644 --- a/code/modules/atmospherics/environmental/LINDA_turf_tile.dm +++ b/code/modules/atmospherics/environmental/LINDA_turf_tile.dm @@ -49,6 +49,7 @@ SSair.add_to_active(T) return ..() +/// Function for Extools Atmos /turf/proc/update_air_ref() /////////////////GAS MIXTURE PROCS/////////////////// diff --git a/code/modules/atmospherics/gasmixtures/gas_mixture.dm b/code/modules/atmospherics/gasmixtures/gas_mixture.dm index 4c71815c9c..f310f17b04 100644 --- a/code/modules/atmospherics/gasmixtures/gas_mixture.dm +++ b/code/modules/atmospherics/gasmixtures/gas_mixture.dm @@ -359,3 +359,28 @@ get_true_breath_pressure(pp) --> gas_pp = pp/breath_pp*total_moles() to_chat(src, "Total time (new gas mixture): [total_time]ms") to_chat(src, "Operations per second: [100000 / (total_time/1000)]") */ + +/// Releases gas from src to output air. This means that it can not transfer air to gas mixture with higher pressure. +/// a global proc due to rustmos +/proc/release_gas_to(datum/gas_mixture/input_air, datum/gas_mixture/output_air, target_pressure) + var/output_starting_pressure = output_air.return_pressure() + var/input_starting_pressure = input_air.return_pressure() + + if(output_starting_pressure >= min(target_pressure,input_starting_pressure-10)) + //No need to pump gas if target is already reached or input pressure is too low + //Need at least 10 KPa difference to overcome friction in the mechanism + return FALSE + + //Calculate necessary moles to transfer using PV = nRT + if((input_air.total_moles() > 0) && (input_air.return_temperature()>0)) + var/pressure_delta = min(target_pressure - output_starting_pressure, (input_starting_pressure - output_starting_pressure)/2) + //Can not have a pressure delta that would cause output_pressure > input_pressure + + var/transfer_moles = pressure_delta*output_air.return_volume()/(input_air.return_temperature() * R_IDEAL_GAS_EQUATION) + + //Actually transfer the gas + var/datum/gas_mixture/removed = input_air.remove(transfer_moles) + output_air.merge(removed) + + return TRUE + return FALSE diff --git a/code/modules/atmospherics/gasmixtures/reactions.dm b/code/modules/atmospherics/gasmixtures/reactions.dm index 7f073567c5..5f425d87ff 100644 --- a/code/modules/atmospherics/gasmixtures/reactions.dm +++ b/code/modules/atmospherics/gasmixtures/reactions.dm @@ -256,6 +256,8 @@ /datum/gas_reaction/fusion/react(datum/gas_mixture/air, datum/holder) var/turf/open/location + if (isopenturf(holder)) + return if (istype(holder,/datum/pipeline)) //Find the tile the reaction is occuring on, or a random part of the network if it's a pipenet. var/datum/pipeline/fusion_pipenet = holder location = get_turf(pick(fusion_pipenet.members)) @@ -356,7 +358,7 @@ /datum/gas/oxygen = 20, /datum/gas/nitrogen = 20, /datum/gas/nitrous_oxide = 5, - "TEMP" = FIRE_MINIMUM_TEMPERATURE_TO_EXIST*400 + "TEMP" = FIRE_MINIMUM_TEMPERATURE_TO_EXIST*25 ) /datum/gas_reaction/nitrylformation/react(datum/gas_mixture/air) @@ -367,8 +369,8 @@ var/energy_used = heat_efficency*NITRYL_FORMATION_ENERGY if ((air.get_moles(/datum/gas/oxygen) - heat_efficency < 0 )|| (air.get_moles(/datum/gas/nitrogen) - heat_efficency < 0)) //Shouldn't produce gas from nothing. return NO_REACTION - air.adjust_moles(/datum/gas/oxygen, heat_efficency) - air.adjust_moles(/datum/gas/nitrogen, heat_efficency) + air.adjust_moles(/datum/gas/oxygen, -heat_efficency) + air.adjust_moles(/datum/gas/nitrogen, -heat_efficency) air.adjust_moles(/datum/gas/nitryl, heat_efficency*2) if(energy_used > 0) @@ -497,7 +499,7 @@ min_requirements = list( /datum/gas/nitrogen = 10, /datum/gas/tritium = 5, - "TEMP" = 5000000) + "ENER" = NOBLIUM_FORMATION_ENERGY) /datum/gas_reaction/nobliumformation/react(datum/gas_mixture/air) var/old_heat_capacity = air.heat_capacity() diff --git a/code/modules/atmospherics/machinery/airalarm.dm b/code/modules/atmospherics/machinery/airalarm.dm index 6049ee965e..c63797282d 100644 --- a/code/modules/atmospherics/machinery/airalarm.dm +++ b/code/modules/atmospherics/machinery/airalarm.dm @@ -304,7 +304,7 @@ "danger_level" = cur_tlv.get_danger_level(environment.get_moles(gas_id) * partial_pressure) )) - if(!locked || hasSiliconAccessInArea(user, PRIVILEDGES_SILICON|PRIVILEDGES_DRONE)) + if(!locked || hasSiliconAccessInArea(user, PRIVILEGES_SILICON|PRIVILEGES_DRONE)) data["vents"] = list() for(var/id_tag in A.air_vent_names) var/long_name = A.air_vent_names[id_tag] @@ -385,13 +385,13 @@ if(..() || buildstage != 2) return var/silicon_access = hasSiliconAccessInArea(usr) - var/bot_priviledges = silicon_access || (usr.silicon_privileges & PRIVILEDGES_DRONE) - if((locked && !bot_priviledges) || (silicon_access && aidisabled)) + var/bot_privileges = silicon_access || (usr.silicon_privileges & PRIVILEGES_DRONE) + if((locked && !bot_privileges) || (silicon_access && aidisabled)) return var/device_id = params["id_tag"] switch(action) if("lock") - if(bot_priviledges && !wires.is_cut(WIRE_IDSCAN)) + if(bot_privileges && !wires.is_cut(WIRE_IDSCAN)) locked = !locked . = TRUE if("power", "toggle_filter", "widenet", "scrubbing") @@ -762,14 +762,14 @@ /obj/machinery/airalarm/attackby(obj/item/W, mob/user, params) switch(buildstage) if(2) - if(istype(W, /obj/item/wirecutters) && panel_open && wires.is_all_cut()) + if(W.tool_behaviour == TOOL_WIRECUTTER && panel_open && wires.is_all_cut()) W.play_tool_sound(src) to_chat(user, "You cut the final wires.") new /obj/item/stack/cable_coil(loc, 5) buildstage = 1 update_icon() return - else if(istype(W, /obj/item/screwdriver)) // Opening that Air Alarm up. + else if(W.tool_behaviour == TOOL_SCREWDRIVER) // Opening that Air Alarm up. W.play_tool_sound(src) panel_open = !panel_open to_chat(user, "The wires have been [panel_open ? "exposed" : "unexposed"].") @@ -781,7 +781,7 @@ wires.interact(user) return if(1) - if(istype(W, /obj/item/crowbar)) + if(W.tool_behaviour == TOOL_CROWBAR) user.visible_message("[user.name] removes the electronics from [src.name].",\ "You start prying out the circuit...") W.play_tool_sound(src) @@ -832,7 +832,7 @@ update_icon() return - if(istype(W, /obj/item/wrench)) + if(W.tool_behaviour == TOOL_WRENCH) to_chat(user, "You detach \the [src] from the wall.") W.play_tool_sound(src) new /obj/item/wallframe/airalarm( user.loc ) diff --git a/code/modules/atmospherics/machinery/atmosmachinery.dm b/code/modules/atmospherics/machinery/atmosmachinery.dm index 25e4084524..7f4a8cd794 100644 --- a/code/modules/atmospherics/machinery/atmosmachinery.dm +++ b/code/modules/atmospherics/machinery/atmosmachinery.dm @@ -44,7 +44,7 @@ . = ..() if(is_type_in_list(src, GLOB.ventcrawl_machinery) && isliving(user)) var/mob/living/L = user - if(L.ventcrawler) + if(SEND_SIGNAL(L, COMSIG_CHECK_VENTCRAWL)) . += "Alt-click to crawl through it." /obj/machinery/atmospherics/New(loc, process = TRUE, setdir) @@ -318,7 +318,7 @@ /obj/machinery/atmospherics/AltClick(mob/living/L) if(is_type_in_typecache(src, GLOB.ventcrawl_machinery)) - return L.handle_ventcrawl(src) + return SEND_SIGNAL(L, COMSIG_HANDLE_VENTCRAWL, src) return ..() diff --git a/code/modules/atmospherics/machinery/components/binary_devices/passive_gate.dm b/code/modules/atmospherics/machinery/components/binary_devices/passive_gate.dm index 00a085c31b..56a7d78288 100644 --- a/code/modules/atmospherics/machinery/components/binary_devices/passive_gate.dm +++ b/code/modules/atmospherics/machinery/components/binary_devices/passive_gate.dm @@ -55,26 +55,7 @@ Passive gate is similar to the regular pump except: var/datum/gas_mixture/air1 = airs[1] var/datum/gas_mixture/air2 = airs[2] - - var/output_starting_pressure = air2.return_pressure() - var/input_starting_pressure = air1.return_pressure() - - if(output_starting_pressure >= min(target_pressure,input_starting_pressure-10)) - //No need to pump gas if target is already reached or input pressure is too low - //Need at least 10 KPa difference to overcome friction in the mechanism - return - - //Calculate necessary moles to transfer using PV = nRT - if((air1.total_moles() > 0) && (air1.return_temperature()>0)) - var/pressure_delta = min(target_pressure - output_starting_pressure, (input_starting_pressure - output_starting_pressure)/2) - //Can not have a pressure delta that would cause output_pressure > input_pressure - - var/transfer_moles = pressure_delta*air2.return_volume()/(air1.return_temperature() * R_IDEAL_GAS_EQUATION) - - //Actually transfer the gas - var/datum/gas_mixture/removed = air1.remove(transfer_moles) - air2.merge(removed) - + if(release_gas_to(air1, air2, target_pressure)) update_parents() diff --git a/code/modules/atmospherics/machinery/components/trinary_devices/filter.dm b/code/modules/atmospherics/machinery/components/trinary_devices/filter.dm index ffab6a885c..11c54409f6 100644 --- a/code/modules/atmospherics/machinery/components/trinary_devices/filter.dm +++ b/code/modules/atmospherics/machinery/components/trinary_devices/filter.dm @@ -6,7 +6,6 @@ desc = "Very useful for filtering gasses." can_unwrench = TRUE - var/transfer_rate = MAX_TRANSFER_RATE var/filter_type = null var/frequency = 0 diff --git a/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm b/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm index c08eaf2e8a..cf2cdd80a7 100644 --- a/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm +++ b/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm @@ -326,7 +326,7 @@ || default_deconstruction_crowbar(I)) update_icon() return - else if(istype(I, /obj/item/screwdriver)) + else if(I.tool_behaviour == TOOL_SCREWDRIVER) to_chat(user, "You can't access the maintenance panel while the pod is " \ + (on ? "active" : (occupant ? "full" : "open")) + ".") return @@ -454,8 +454,10 @@ return G.return_temperature() return ..() -/obj/machinery/atmospherics/components/unary/cryo_cell/default_change_direction_wrench(mob/user, obj/item/wrench/W) +/obj/machinery/atmospherics/components/unary/cryo_cell/default_change_direction_wrench(mob/user, obj/item/W) . = ..() + if(!W.tool_behaviour == TOOL_WRENCH) + return if(.) SetInitDirections() var/obj/machinery/atmospherics/node = nodes[1] diff --git a/code/modules/atmospherics/machinery/components/unary_devices/tank.dm b/code/modules/atmospherics/machinery/components/unary_devices/tank.dm index 2f3372462d..e96ccdb25b 100644 --- a/code/modules/atmospherics/machinery/components/unary_devices/tank.dm +++ b/code/modules/atmospherics/machinery/components/unary_devices/tank.dm @@ -2,14 +2,17 @@ /obj/machinery/atmospherics/components/unary/tank icon = 'icons/obj/atmospherics/pipes/pressure_tank.dmi' icon_state = "generic" + name = "pressure tank" desc = "A large vessel containing pressurized gas." + max_integrity = 800 density = TRUE layer = ABOVE_WINDOW_LAYER - plane = GAME_PLANE pipe_flags = PIPING_ONE_PER_TURF + var/volume = 10000 //in liters + /// The typepath of the gas this tank should be filled with. var/gas_type = 0 /obj/machinery/atmospherics/components/unary/tank/New() @@ -20,6 +23,7 @@ if(gas_type) air_contents.set_moles(AIR_CONTENTS) name = "[name] ([GLOB.meta_gas_names[gas_type]])" + setPipingLayer(piping_layer) /obj/machinery/atmospherics/components/unary/tank/air icon_state = "grey" @@ -38,15 +42,71 @@ icon_state = "orange" gas_type = /datum/gas/plasma -/obj/machinery/atmospherics/components/unary/tank/oxygen - icon_state = "blue" - gas_type = /datum/gas/oxygen - /obj/machinery/atmospherics/components/unary/tank/nitrogen icon_state = "red" gas_type = /datum/gas/nitrogen -/obj/machinery/atmospherics/components/unary/tank/nitrous_oxide +/obj/machinery/atmospherics/components/unary/tank/oxygen + icon_state = "blue" + gas_type = /datum/gas/oxygen + +/obj/machinery/atmospherics/components/unary/tank/nitrous icon_state = "red_white" gas_type = /datum/gas/nitrous_oxide +/obj/machinery/atmospherics/components/unary/tank/bz + gas_type = /datum/gas/bz + +// /obj/machinery/atmospherics/components/unary/tank/freon +// icon_state = "blue" +// gas_type = /datum/gas/freon + +// /obj/machinery/atmospherics/components/unary/tank/halon +// icon_state = "blue" +// gas_type = /datum/gas/halon + +// /obj/machinery/atmospherics/components/unary/tank/healium +// icon_state = "red" +// gas_type = /datum/gas/healium + +// /obj/machinery/atmospherics/components/unary/tank/hydrogen +// icon_state = "grey" +// gas_type = /datum/gas/hydrogen + +/obj/machinery/atmospherics/components/unary/tank/hypernoblium + icon_state = "blue" + gas_type = /datum/gas/hypernoblium + +/obj/machinery/atmospherics/components/unary/tank/miasma + gas_type = /datum/gas/miasma + +/obj/machinery/atmospherics/components/unary/tank/nitryl + gas_type = /datum/gas/nitryl + +/obj/machinery/atmospherics/components/unary/tank/pluoxium + icon_state = "blue" + gas_type = /datum/gas/pluoxium + +// /obj/machinery/atmospherics/components/unary/tank/proto_nitrate +// icon_state = "red" +// gas_type = /datum/gas/proto_nitrate + +/obj/machinery/atmospherics/components/unary/tank/stimulum + icon_state = "red" + gas_type = /datum/gas/stimulum + +/obj/machinery/atmospherics/components/unary/tank/tritium + gas_type = /datum/gas/tritium + +/obj/machinery/atmospherics/components/unary/tank/water_vapor + icon_state = "grey" + gas_type = /datum/gas/water_vapor + +// /obj/machinery/atmospherics/components/unary/tank/zauker +// gas_type = /datum/gas/zauker + +// /obj/machinery/atmospherics/components/unary/tank/helium +// gas_type = /datum/gas/helium + +// /obj/machinery/atmospherics/components/unary/tank/antinoblium +// gas_type = /datum/gas/antinoblium diff --git a/code/modules/atmospherics/machinery/portable/canister.dm b/code/modules/atmospherics/machinery/portable/canister.dm index 0eebf22be4..9c3e0ef64a 100644 --- a/code/modules/atmospherics/machinery/portable/canister.dm +++ b/code/modules/atmospherics/machinery/portable/canister.dm @@ -5,22 +5,28 @@ desc = "A canister for the storage of gas." icon_state = "yellow" density = TRUE - - var/valve_open = FALSE - var/obj/machinery/atmospherics/components/binary/passive_gate/pump - var/release_log = "" - volume = 1000 - var/filled = 0.5 - var/gas_type - var/release_pressure = ONE_ATMOSPHERE - var/can_max_release_pressure = (ONE_ATMOSPHERE * 10) - var/can_min_release_pressure = (ONE_ATMOSPHERE / 10) - armor = list("melee" = 50, "bullet" = 50, "laser" = 50, "energy" = 100, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 50) max_integrity = 250 integrity_failure = 0.4 pressure_resistance = 7 * ONE_ATMOSPHERE + + var/valve_open = FALSE + var/release_log = "" + + var/filled = 0.5 + var/gas_type + + var/release_pressure = ONE_ATMOSPHERE + var/can_max_release_pressure = (ONE_ATMOSPHERE * 10) + var/can_min_release_pressure = (ONE_ATMOSPHERE / 10) + + // this removes atmos fusion cans** + ///Max amount of heat allowed inside of the canister before it starts to melt (different tiers have different limits) + // var/heat_limit = 5000 + ///Max amount of pressure allowed inside of the canister before it starts to break (different tiers have different limits) + // var/pressure_limit = 50000 + var/temperature_resistance = 1000 + T0C var/starter_temp // Prototype vars @@ -32,6 +38,8 @@ var/maximum_timer_set = 300 var/timing = FALSE var/restricted = FALSE + ///Set the tier of the canister and overlay used + // var/mode = CANISTER_TIER_1 req_access = list() var/update = 0 @@ -186,7 +194,6 @@ can_min_release_pressure = (ONE_ATMOSPHERE / 30) prototype = TRUE - /obj/machinery/portable_atmospherics/canister/proto/default/oxygen name = "prototype canister" desc = "A prototype canister for a prototype bike, what could go wrong?" @@ -195,27 +202,18 @@ filled = 1 release_pressure = ONE_ATMOSPHERE*2 -/obj/machinery/portable_atmospherics/canister/New(loc, datum/gas_mixture/existing_mixture) - ..() +/obj/machinery/portable_atmospherics/canister/Initialize(mapload, datum/gas_mixture/existing_mixture) + . = ..() if(existing_mixture) air_contents.copy_from(existing_mixture) else create_gas() - pump = new(src, FALSE) - pump.on = TRUE - pump.stat = 0 - SSair.add_to_rebuild_queue(pump) - update_icon() -/obj/machinery/portable_atmospherics/canister/Destroy() - qdel(pump) - pump = null - return ..() - /obj/machinery/portable_atmospherics/canister/proc/create_gas() if(gas_type) + // air_contents.add_gas(gas_type) if(starter_temp) air_contents.set_temperature(starter_temp) air_contents.set_moles(gas_type,(maximum_pressure * filled) * air_contents.return_volume() / (R_IDEAL_GAS_EQUATION * air_contents.return_temperature())) @@ -223,8 +221,10 @@ air_contents.set_temperature(starter_temp) /obj/machinery/portable_atmospherics/canister/air/create_gas() - air_contents.set_moles(/datum/gas/oxygen, (O2STANDARD * maximum_pressure * filled) * air_contents.return_volume() / (R_IDEAL_GAS_EQUATION * air_contents.return_temperature())) - air_contents.set_moles(/datum/gas/nitrogen, (N2STANDARD * maximum_pressure * filled) * air_contents.return_volume() / (R_IDEAL_GAS_EQUATION * air_contents.return_temperature())) + var/oh_two = /datum/gas/oxygen + var/dihydrogen = /datum/gas/nitrogen //how to piss of chemists + air_contents.set_moles(oh_two, (O2STANDARD * maximum_pressure * filled) * air_contents.return_volume() / (R_IDEAL_GAS_EQUATION * air_contents.return_temperature())) + air_contents.set_moles(dihydrogen, (N2STANDARD * maximum_pressure * filled) * air_contents.return_volume() / (R_IDEAL_GAS_EQUATION * air_contents.return_temperature())) /obj/machinery/portable_atmospherics/canister/update_icon_state() if(stat & BROKEN) @@ -261,19 +261,22 @@ new /obj/item/stack/sheet/metal (loc, 5) qdel(src) -obj/machinery/portable_atmospherics/canister/welder_act(mob/living/user, obj/item/I) +/obj/machinery/portable_atmospherics/canister/welder_act(mob/living/user, obj/item/I) ..() if(user.a_intent == INTENT_HARM) return FALSE - if(stat & BROKEN) - if(!I.tool_start_check(user, amount=0)) - return TRUE - to_chat(user, "You begin cutting [src] apart...") - if(I.use_tool(src, user, 30, volume=50)) - deconstruct(TRUE) - else - to_chat(user, "You cannot slice [src] apart when it isn't broken.") + if(!I.tool_start_check(user, amount=0)) + return TRUE + var/pressure = air_contents.return_pressure() + if(pressure > 300) + to_chat(user, "The pressure gauge on \the [src] indicates a high pressure inside... maybe you want to reconsider?") + to_chat(user, "You begin cutting \the [src] apart...") + if(I.use_tool(src, user, 3 SECONDS, volume=50)) + to_chat(user, "You cut \the [src] apart.") + deconstruct(TRUE) + message_admins("[src] deconstructed by [ADMIN_LOOKUPFLW(user)]") + log_game("[src] deconstructed by [key_name(user)]") return TRUE @@ -316,19 +319,20 @@ obj/machinery/portable_atmospherics/canister/welder_act(mob/living/user, obj/ite if(timing && valve_timer < world.time) valve_open = !valve_open timing = FALSE - if(!valve_open) - pump.airs[1] = null - pump.airs[2] = null - return + if(valve_open) + var/turf/T = get_turf(src) + var/datum/gas_mixture/target_air = holding ? holding.air_contents : T.return_air() - var/turf/T = get_turf(src) - pump.airs[1] = air_contents - pump.airs[2] = holding ? holding.air_contents : T.return_air() - pump.target_pressure = release_pressure + if(release_gas_to(air_contents, target_air, release_pressure) && !holding) + air_update_turf() - pump.process_atmos() // Pump gas. - if(!holding) - air_update_turf() // Update the environment if needed. + // var/our_pressure = air_contents.return_pressure() + // var/our_temperature = air_contents.return_temperature() + + ///function used to check the limit of the canisters and also set the amount of damage that the canister can receive, if the heat and pressure are way higher than the limit the more damage will be done + // currently unused + // if(our_temperature > heat_limit || our_pressure > pressure_limit) + // take_damage(clamp((our_temperature/heat_limit) * (our_pressure/pressure_limit) * delta_time * 2, 5, 50), BURN, 0) update_icon() /obj/machinery/portable_atmospherics/canister/ui_state(mob/user) @@ -340,35 +344,48 @@ obj/machinery/portable_atmospherics/canister/welder_act(mob/living/user, obj/ite ui = new(user, src, "Canister", name) ui.open() +/obj/machinery/portable_atmospherics/canister/ui_static_data(mob/user) + return list( + "defaultReleasePressure" = round(CAN_DEFAULT_RELEASE_PRESSURE), + "minReleasePressure" = round(can_min_release_pressure), + "maxReleasePressure" = round(can_max_release_pressure), + "pressureLimit" = round(1e14), + "holdingTankLeakPressure" = round(TANK_LEAK_PRESSURE), + "holdingTankFragPressure" = round(TANK_FRAGMENT_PRESSURE) + ) + /obj/machinery/portable_atmospherics/canister/ui_data() - var/data = list() - data["portConnected"] = connected_port ? 1 : 0 - data["tankPressure"] = round(air_contents.return_pressure() ? air_contents.return_pressure() : 0) - data["releasePressure"] = round(release_pressure ? release_pressure : 0) - data["defaultReleasePressure"] = round(CAN_DEFAULT_RELEASE_PRESSURE) - data["minReleasePressure"] = round(can_min_release_pressure) - data["maxReleasePressure"] = round(can_max_release_pressure) - data["valveOpen"] = valve_open ? 1 : 0 + . = list( + "portConnected" = !!connected_port, + "tankPressure" = round(air_contents.return_pressure()), + "releasePressure" = round(release_pressure), + "valveOpen" = !!valve_open, + "isPrototype" = !!prototype, + "hasHoldingTank" = !!holding + ) - data["isPrototype"] = prototype ? 1 : 0 if (prototype) - data["restricted"] = restricted - data["timing"] = timing - data["time_left"] = get_time_left() - data["timer_set"] = timer_set - data["timer_is_not_default"] = timer_set != default_timer_set - data["timer_is_not_min"] = timer_set != minimum_timer_set - data["timer_is_not_max"] = timer_set != maximum_timer_set + . += list( + "restricted" = restricted, + "timing" = timing, + "time_left" = get_time_left(), + "timer_set" = timer_set, + "timer_is_not_default" = timer_set != default_timer_set, + "timer_is_not_min" = timer_set != minimum_timer_set, + "timer_is_not_max" = timer_set != maximum_timer_set + ) - data["hasHoldingTank"] = holding ? 1 : 0 if (holding) - data["holdingTank"] = list() - data["holdingTank"]["name"] = holding.name - data["holdingTank"]["tankPressure"] = round(holding.air_contents.return_pressure()) - return data + . += list( + "holdingTank" = list( + "name" = holding.name, + "tankPressure" = round(holding.air_contents.return_pressure()) + ) + ) /obj/machinery/portable_atmospherics/canister/ui_act(action, params) - if(..()) + . = ..() + if(.) return switch(action) if("relabel") @@ -377,6 +394,7 @@ obj/machinery/portable_atmospherics/canister/welder_act(mob/living/user, obj/ite var/newtype = label2types[label] if(newtype) var/obj/machinery/portable_atmospherics/canister/replacement = newtype + investigate_log("was relabelled to [initial(replacement.name)] by [key_name(usr)].", INVESTIGATE_ATMOS) name = initial(replacement.name) desc = initial(replacement.desc) icon_state = initial(replacement.icon_state) @@ -458,9 +476,8 @@ obj/machinery/portable_atmospherics/canister/welder_act(mob/living/user, obj/ite if("eject") if(holding) if(valve_open) - message_admins("[ADMIN_LOOKUPFLW(usr)] removed [holding] from [src] with valve still open at [ADMIN_VERBOSEJMP(src)] releasing contents into the air
    .") - investigate_log("[key_name(usr)] removed the [holding], leaving the valve open and transferring into the air
    ", INVESTIGATE_ATMOS) - holding.forceMove(get_turf(src)) - holding = null + message_admins("[ADMIN_LOOKUPFLW(usr)] removed [holding] from [src] with valve still open at [ADMIN_VERBOSEJMP(src)] releasing contents into the air.") + investigate_log("[key_name(usr)] removed the [holding], leaving the valve open and transferring into the air.", INVESTIGATE_ATMOS) + replace_tank(usr, FALSE) . = TRUE update_icon() diff --git a/code/modules/atmospherics/machinery/portable/portable_atmospherics.dm b/code/modules/atmospherics/machinery/portable/portable_atmospherics.dm index fa57e683c4..166727f6ae 100644 --- a/code/modules/atmospherics/machinery/portable/portable_atmospherics.dm +++ b/code/modules/atmospherics/machinery/portable/portable_atmospherics.dm @@ -115,7 +115,7 @@ to_chat(user, "[holding ? "In one smooth motion you pop [holding] out of [src]'s connector and replace it with [T]" : "You insert [T] into [src]"].") replace_tank(user, FALSE, T) update_icon() - else if(istype(W, /obj/item/wrench)) + else if(W.tool_behaviour == TOOL_WRENCH) if(!(stat & BROKEN)) if(connected_port) disconnect() diff --git a/code/modules/atmospherics/multiz.dm b/code/modules/atmospherics/multiz.dm index 79a5c2cf14..cb52d03ff6 100644 --- a/code/modules/atmospherics/multiz.dm +++ b/code/modules/atmospherics/multiz.dm @@ -1,4 +1,5 @@ -obj/machinery/atmospherics/pipe/simple/multiz ///This is an atmospherics pipe which can relay air up a deck (Z+1). It currently only supports being on pipe layer 1 +/// This is an atmospherics pipe which can relay air up/down a deck. +/obj/machinery/atmospherics/pipe/simple/multiz name = "multi deck pipe adapter" desc = "An adapter which allows pipes to connect to other pipenets on different decks." icon_state = "multiz_pipe" @@ -24,6 +25,4 @@ obj/machinery/atmospherics/pipe/simple/multiz ///This is an atmospherics pipe wh if(above) nodes += above above.nodes += src //Two way travel :) - return ..() - else - return ..() + return ..() diff --git a/code/modules/awaymissions/capture_the_flag.dm b/code/modules/awaymissions/capture_the_flag.dm index 3426208fae..d32905e007 100644 --- a/code/modules/awaymissions/capture_the_flag.dm +++ b/code/modules/awaymissions/capture_the_flag.dm @@ -7,6 +7,8 @@ #define AMMO_DROP_LIFETIME 300 #define CTF_REQUIRED_PLAYERS 4 + + /obj/item/ctf name = "banner" icon = 'icons/obj/items_and_weapons.dmi' @@ -16,13 +18,13 @@ righthand_file = 'icons/mob/inhands/equipment/banners_righthand.dmi' desc = "A banner with Nanotrasen's logo on it." slowdown = 2 - item_flags = SLOWS_WHILE_IN_HAND throw_speed = 0 throw_range = 1 force = 200 armour_penetration = 1000 resistance_flags = INDESTRUCTIBLE anchored = TRUE + item_flags = SLOWS_WHILE_IN_HAND var/team = WHITE_TEAM var/reset_cooldown = 0 var/anyonecanpickup = TRUE @@ -53,12 +55,13 @@ to_chat(M, "\The [src] has been returned to base!") STOP_PROCESSING(SSobj, src) -/obj/item/ctf/on_attack_hand(mob/living/user, act_intent = user.a_intent, unarmed_attack_flags) +//ATTACK HAND IGNORING PARENT RETURN VALUE +/obj/item/ctf/on_attack_hand(mob/living/user) if(!is_ctf_target(user) && !anyonecanpickup) - to_chat(user, "Non players shouldn't be moving the flag!") + to_chat(user, "Non-players shouldn't be moving the flag!") return if(team in user.faction) - to_chat(user, "You can't move your own flag!") + to_chat(user, "You can't move your own flag!") return if(loc == user) if(!user.dropItemToGround(src)) @@ -68,7 +71,7 @@ if(!user.put_in_active_hand(src)) dropped(user) return - user.anchored = TRUE + user.set_anchored(TRUE) user.status_flags &= ~CANPUSH for(var/mob/M in GLOB.player_list) var/area/mob_area = get_area(M) @@ -79,7 +82,7 @@ /obj/item/ctf/dropped(mob/user) ..() - user.anchored = FALSE + user.set_anchored(FALSE) user.status_flags |= CANPUSH reset_cooldown = world.time + 200 //20 seconds START_PROCESSING(SSobj, src) @@ -172,20 +175,20 @@ GLOB.poi_list.Remove(src) ..() -/obj/machinery/capture_the_flag/process() +/obj/machinery/capture_the_flag/process(delta_time) for(var/i in spawned_mobs) if(!i) spawned_mobs -= i continue // Anyone in crit, automatically reap - var/mob/living/M = i - if(M.InCritical() || M.stat == DEAD) - ctf_dust_old(M) + var/mob/living/living_participant = i + if(living_participant.InCritical() || living_participant.stat == DEAD) + ctf_dust_old(living_participant) else // The changes that you've been hit with no shield but not // instantly critted are low, but have some healing. - M.adjustBruteLoss(-5) - M.adjustFireLoss(-5) + living_participant.adjustBruteLoss(-2.5 * delta_time) + living_participant.adjustFireLoss(-2.5 * delta_time) /obj/machinery/capture_the_flag/red name = "Red CTF Controller" @@ -212,6 +215,10 @@ toggle_all_ctf(user) return + + // if(!(GLOB.ghost_role_flags & GHOSTROLE_MINIGAME)) + // to_chat(user, "CTF has been temporarily disabled by admins.") + // return people_who_want_to_play |= user.ckey var/num = people_who_want_to_play.len var/remaining = CTF_REQUIRED_PLAYERS - num @@ -227,7 +234,7 @@ return if(user.ckey in team_members) if(user.ckey in recently_dead_ckeys) - to_chat(user, "It must be more than [DisplayTimeText(respawn_cooldown)] from your last death to respawn!") + to_chat(user, "It must be more than [DisplayTimeText(respawn_cooldown)] from your last death to respawn!") return var/client/new_team_member = user.client if(user.mind && user.mind.current) @@ -239,10 +246,10 @@ if(CTF == src || CTF.ctf_enabled == FALSE) continue if(user.ckey in CTF.team_members) - to_chat(user, "No switching teams while the round is going!") + to_chat(user, "No switching teams while the round is going!") return if(CTF.team_members.len < src.team_members.len) - to_chat(user, "[src.team] has more team members than [CTF.team]. Try joining [CTF.team] team to even things up.") + to_chat(user, "[src.team] has more team members than [CTF.team]! Try joining [CTF.team] team to even things up.") return team_members |= user.ckey var/client/new_team_member = user.client @@ -258,7 +265,7 @@ addtimer(CALLBACK(src, .proc/clear_cooldown, body.ckey), respawn_cooldown, TIMER_UNIQUE) body.dust() -/obj/machinery/capture_the_flag/proc/clear_cooldown(var/ckey) +/obj/machinery/capture_the_flag/proc/clear_cooldown(ckey) recently_dead_ckeys -= ckey /obj/machinery/capture_the_flag/proc/spawn_team_member(client/new_team_member) @@ -270,7 +277,7 @@ M.equipOutfit(ctf_gear) M.dna.species.punchdamagehigh = 25 M.dna.species.punchdamagelow = 25 - M.AddElement(/datum/element/ghost_role_eligibility) + M.AddElement(/datum/element/ghost_role_eligibility) //?? spawned_mobs += M /obj/machinery/capture_the_flag/Topic(href, href_list) @@ -293,14 +300,15 @@ victory() /obj/machinery/capture_the_flag/proc/victory() - for(var/mob/M in GLOB.mob_list) - var/area/mob_area = get_area(M) + for(var/mob/_competitor in GLOB.mob_living_list) + var/mob/living/competitor = _competitor + var/area/mob_area = get_area(competitor) if(istype(mob_area, /area/ctf)) - to_chat(M, "[team] team wins!") - to_chat(M, "Teams have been cleared. Click on the machines to vote to begin another round.") - for(var/obj/item/ctf/W in M) - M.dropItemToGround(W) - M.dust() + to_chat(competitor, "[team] team wins!") + to_chat(competitor, "Teams have been cleared. Click on the machines to vote to begin another round.") + for(var/obj/item/ctf/W in competitor) + competitor.dropItemToGround(W) + competitor.dust() for(var/obj/machinery/control_point/control in GLOB.machines) control.icon_state = "dominator" control.controlling = null @@ -328,7 +336,7 @@ dead_barricades.Cut() - notify_ghosts("[name] has been activated!", enter_link="(Click to join the [team] team!) or click on the controller directly!", source = src, action=NOTIFY_ATTACK) + notify_ghosts("[name] has been activated!", enter_link="(Click to join the [team] team!) or click on the controller directly!", source = src, action=NOTIFY_ATTACK, header = "CTF has been activated") if(!arena_reset) reset_the_arena() @@ -355,10 +363,10 @@ ctf_enabled = FALSE arena_reset = FALSE var/area/A = get_area(src) - for(var/i in GLOB.mob_list) - var/mob/M = i - if((get_area(A) == A) && (M.ckey in team_members)) - M.dust() + for(var/_competitor in GLOB.mob_living_list) + var/mob/living/competitor = _competitor + if((get_area(A) == A) && (competitor.ckey in team_members)) + competitor.dust() team_members.Cut() spawned_mobs.Cut() recently_dead_ckeys.Cut() @@ -375,18 +383,18 @@ CTF.ctf_gear = initial(ctf_gear) CTF.respawn_cooldown = DEFAULT_RESPAWN -/proc/ctf_floor_vanish(atom/target) - if(isturf(target.loc)) - qdel(target) - /obj/item/gun/ballistic/automatic/pistol/deagle/ctf desc = "This looks like it could really hurt in melee." force = 75 mag_type = /obj/item/ammo_box/magazine/m50/ctf -/obj/item/gun/ballistic/automatic/pistol/deagle/ctf/dropped(mob/user) +/obj/item/gun/ballistic/automatic/pistol/deagle/ctf/dropped() . = ..() - addtimer(CALLBACK(GLOBAL_PROC, /proc/ctf_floor_vanish, src), 1) + addtimer(CALLBACK(src, .proc/floor_vanish), 1) + +/obj/item/gun/ballistic/automatic/pistol/deagle/ctf/proc/floor_vanish() + if(isturf(loc)) + qdel(src) /obj/item/ammo_box/magazine/m50/ctf ammo_type = /obj/item/ammo_casing/a50/ctf @@ -400,6 +408,7 @@ /obj/item/projectile/bullet/ctf/prehit(atom/target) if(is_ctf_target(target)) damage = 60 + return //PROJECTILE_PIERCE_NONE /// hey uhh don't hit anyone behind them . = ..() /obj/item/gun/ballistic/automatic/laser/ctf @@ -407,16 +416,24 @@ desc = "This looks like it could really hurt in melee." force = 50 -/obj/item/gun/ballistic/automatic/laser/ctf/dropped(mob/user) +/obj/item/gun/ballistic/automatic/laser/ctf/dropped() . = ..() - addtimer(CALLBACK(GLOBAL_PROC, /proc/ctf_floor_vanish, src), 1) + addtimer(CALLBACK(src, .proc/floor_vanish), 1) + +/obj/item/gun/ballistic/automatic/laser/ctf/proc/floor_vanish() + if(isturf(loc)) + qdel(src) /obj/item/ammo_box/magazine/recharge/ctf ammo_type = /obj/item/ammo_casing/caseless/laser/ctf -/obj/item/ammo_box/magazine/recharge/ctf/dropped(mob/user) +/obj/item/ammo_box/magazine/recharge/ctf/dropped() . = ..() - addtimer(CALLBACK(GLOBAL_PROC, /proc/ctf_floor_vanish, src), 1) + addtimer(CALLBACK(src, .proc/floor_vanish), 1) + +/obj/item/ammo_box/magazine/recharge/ctf/proc/floor_vanish() + if(isturf(loc)) + qdel(src) /obj/item/ammo_casing/caseless/laser/ctf projectile_type = /obj/item/projectile/beam/ctf @@ -428,15 +445,16 @@ /obj/item/projectile/beam/ctf/prehit(atom/target) if(is_ctf_target(target)) damage = 150 + return //PROJECTILE_PIERCE_NONE /// hey uhhh don't hit anyone behind them . = ..() /proc/is_ctf_target(atom/target) . = FALSE if(istype(target, /obj/structure/barricade/security/ctf)) . = TRUE - if(isliving(target)) - var/mob/living/H = target - if((RED_TEAM in H.faction) || (BLUE_TEAM in H.faction)) + if(ishuman(target)) + var/mob/living/carbon/human/H = target + if(istype(H.wear_suit, /obj/item/clothing/suit/space/hardsuit/shielded/ctf)) . = TRUE // RED TEAM GUNS @@ -482,7 +500,11 @@ /obj/item/claymore/ctf/dropped(mob/user) . = ..() - addtimer(CALLBACK(GLOBAL_PROC, /proc/ctf_floor_vanish, src), 1) + addtimer(CALLBACK(src, .proc/floor_vanish), 1) + +/obj/item/claymore/ctf/proc/floor_vanish() + if(isturf(loc)) + qdel(src) /datum/outfit/ctf name = "CTF" @@ -491,27 +513,28 @@ suit = /obj/item/clothing/suit/space/hardsuit/shielded/ctf toggle_helmet = FALSE // see the whites of their eyes shoes = /obj/item/clothing/shoes/combat - gloves = /obj/item/clothing/gloves/tackler/combat + gloves = /obj/item/clothing/gloves/combat + id = /obj/item/card/id/away belt = /obj/item/gun/ballistic/automatic/pistol/deagle/ctf l_pocket = /obj/item/ammo_box/magazine/recharge/ctf r_pocket = /obj/item/ammo_box/magazine/recharge/ctf r_hand = /obj/item/gun/ballistic/automatic/laser/ctf back = /obj/item/claymore/ctf -/datum/outfit/ctf/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE, client/preference_source) +/datum/outfit/ctf/post_equip(mob/living/carbon/human/H, visualsOnly=FALSE) if(visualsOnly) return var/list/no_drops = list() var/obj/item/card/id/W = H.wear_id no_drops += W W.registered_name = H.real_name - W.update_label(W.registered_name, W.assignment) + W.update_label() - // The shielded hardsuit is already TRAIT_NODROP - no_drops += H.get_item_by_slot(SLOT_GLOVES) - no_drops += H.get_item_by_slot(SLOT_SHOES) - no_drops += H.get_item_by_slot(SLOT_W_UNIFORM) - no_drops += H.get_item_by_slot(SLOT_EARS) + no_drops += H.get_item_by_slot(ITEM_SLOT_OCLOTHING) + no_drops += H.get_item_by_slot(ITEM_SLOT_GLOVES) + no_drops += H.get_item_by_slot(ITEM_SLOT_FEET) + no_drops += H.get_item_by_slot(ITEM_SLOT_ICLOTHING) + no_drops += H.get_item_by_slot(ITEM_SLOT_EARS) for(var/i in no_drops) var/obj/item/I = i ADD_TRAIT(I, TRAIT_NODROP, CAPTURE_THE_FLAG_TRAIT) @@ -525,6 +548,7 @@ r_hand = /obj/item/gun/ballistic/automatic/laser/ctf/red l_pocket = /obj/item/ammo_box/magazine/recharge/ctf/red r_pocket = /obj/item/ammo_box/magazine/recharge/ctf/red + id = /obj/item/card/id/syndicate_command //it's red /datum/outfit/ctf/red/instagib r_hand = /obj/item/gun/energy/laser/instakill/red @@ -535,12 +559,13 @@ r_hand = /obj/item/gun/ballistic/automatic/laser/ctf/blue l_pocket = /obj/item/ammo_box/magazine/recharge/ctf/blue r_pocket = /obj/item/ammo_box/magazine/recharge/ctf/blue + id = /obj/item/card/id/centcom //it's blue /datum/outfit/ctf/blue/instagib r_hand = /obj/item/gun/energy/laser/instakill/blue shoes = /obj/item/clothing/shoes/jackboots/fast -/datum/outfit/ctf/red/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE, client/preference_source) +/datum/outfit/ctf/red/post_equip(mob/living/carbon/human/H) ..() var/obj/item/radio/R = H.ears R.set_frequency(FREQ_CTF_RED) @@ -548,7 +573,7 @@ R.independent = TRUE H.dna.species.stunmod = 0 -/datum/outfit/ctf/blue/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE, client/preference_source) +/datum/outfit/ctf/blue/post_equip(mob/living/carbon/human/H) ..() var/obj/item/radio/R = H.ears R.set_frequency(FREQ_CTF_BLUE) @@ -595,6 +620,10 @@ /obj/structure/barricade/security/ctf/make_debris() new /obj/effect/ctf/dead_barricade(get_turf(src)) +/obj/structure/table/reinforced/ctf + resistance_flags = INDESTRUCTIBLE + flags_1 = NODECONSTRUCT_1 + /obj/effect/ctf density = FALSE anchored = TRUE @@ -617,10 +646,11 @@ QDEL_IN(src, AMMO_DROP_LIFETIME) /obj/effect/ctf/ammo/Crossed(atom/movable/AM) + . = ..() reload(AM) -/obj/effect/ctf/ammo/Bump(atom/movable/AM) - reload(AM) +/obj/effect/ctf/ammo/Bump(atom/A) + reload(A) /obj/effect/ctf/ammo/Bumped(atom/movable/AM) reload(AM) @@ -636,7 +666,7 @@ qdel(G) O.equip(M) to_chat(M, "Ammunition reloaded!") - playsound(get_turf(M), 'sound/weapons/shotgunpump.ogg', 50, 1, -1) + playsound(get_turf(M), 'sound/weapons/shotgunpump.ogg', 50, TRUE, -1) qdel(src) break @@ -667,18 +697,18 @@ resistance_flags = INDESTRUCTIBLE var/obj/machinery/capture_the_flag/controlling var/team = "none" - var/point_rate = 1 + var/point_rate = 0.5 -/obj/machinery/control_point/process() +/obj/machinery/control_point/process(delta_time) if(controlling) - controlling.control_points += point_rate + controlling.control_points += point_rate * delta_time if(controlling.control_points >= controlling.control_points_to_win) controlling.victory() /obj/machinery/control_point/attackby(mob/user, params) capture(user) -/obj/machinery/control_point/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags) +/obj/machinery/control_point/on_attack_hand(mob/user) capture(user) /obj/machinery/control_point/proc/capture(mob/user) diff --git a/code/modules/awaymissions/corpse.dm b/code/modules/awaymissions/corpse.dm index e68c45a84c..cd5c2f76f3 100644 --- a/code/modules/awaymissions/corpse.dm +++ b/code/modules/awaymissions/corpse.dm @@ -66,7 +66,7 @@ /obj/effect/mob_spawn/Initialize(mapload) . = ..() if(instant || (roundstart && (mapload || (SSticker && SSticker.current_state > GAME_STATE_SETTING_UP)))) - create() + INVOKE_ASYNC(src, .proc/create) else if(ghost_usable) GLOB.poi_list |= src LAZYADD(GLOB.mob_spawners[job_description ? job_description : name], src) diff --git a/code/modules/awaymissions/mission_code/Academy.dm b/code/modules/awaymissions/mission_code/Academy.dm index 129e6d7a2b..98e7f4c292 100644 --- a/code/modules/awaymissions/mission_code/Academy.dm +++ b/code/modules/awaymissions/mission_code/Academy.dm @@ -196,7 +196,7 @@ /obj/item/dice/d20/fate/proc/effect(var/mob/living/carbon/human/user,roll) if(!reusable) used = 1 - visible_message("The die flare briefly.") + visible_message("The die flares briefly.") switch(roll) if(1) //Dust diff --git a/code/modules/awaymissions/mission_code/jungleresort.dm b/code/modules/awaymissions/mission_code/jungleresort.dm index 6c78a81da2..6c82b2c0c4 100644 --- a/code/modules/awaymissions/mission_code/jungleresort.dm +++ b/code/modules/awaymissions/mission_code/jungleresort.dm @@ -18,6 +18,66 @@ hitsound = 'sound/weapons/whip.ogg' icon_state = "whip" +/obj/item/clothing/suit/hooded/wintercoat/captain/jungle + armor = list("melee" = 5, "bullet" = 5, "laser" = 5, "energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0) + +/obj/item/clothing/head/rice_hat/cursed // this was a stupid idea lmao + name = "cursed rice hat" + desc = "Welcome to the rice fields, motherfucker. This particular one seems to give you second thoughts about wearing it." + +/obj/item/clothing/head/rice_hat/cursed/equipped(mob/M, slot) + . = ..() + if (slot == SLOT_HEAD) + RegisterSignal(M, COMSIG_MOB_SAY, .proc/handle_speech) + else + UnregisterSignal(M, COMSIG_MOB_SAY) + +/obj/item/clothing/head/rice_hat/cursed/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, SHAMEBRERO_TRAIT) + +/obj/item/clothing/head/rice_hat/cursed/proc/handle_speech(datum/source, list/speech_args) + var/message = speech_args[SPEECH_MESSAGE] + if(message[1] != "*") + var/list/temp_message = splittext(message, " ") + var/list/pick_list = list() + for(var/i in 1 to temp_message.len) + pick_list += i + for(var/i in 1 to abs(temp_message.len/3)) + var/H = pick(pick_list) + if(findtext(temp_message[H], "*") || findtext(temp_message[H], ";") || findtext(temp_message[H], ":")) + continue + temp_message[H] = ninjaspeak(temp_message[H]) + pick_list -= H + message = temp_message.Join(" ") + + //The Alternate speech mod is now the main one. + message = replacetext(message, "l", "r") + message = replacetext(message, "rr", "ru") + message = replacetext(message, "v", "b") + message = replacetext(message, "f", "hu") + message = replacetext(message, "'t", "") + message = replacetext(message, "t ", "to ") + message = replacetext(message, " I ", " ai ") + message = replacetext(message, "th", "z") + message = replacetext(message, "is", "izu") + message = replacetext(message, "ziz", "zis") + message = replacetext(message, "se", "su") + message = replacetext(message, "br", "bur") + message = replacetext(message, "ry", "ri") + message = replacetext(message, "you", "yuu") + message = replacetext(message, "ck", "cku") + message = replacetext(message, "eu", "uu") + message = replacetext(message, "ow", "au") + message = replacetext(message, "are", "aa") + message = replacetext(message, "ay", "ayu") + message = replacetext(message, "ea", "ii") + message = replacetext(message, "ch", "chi") + message = replacetext(message, "than", "sen") + message = replacetext(message, ".", "") + message = lowertext(message) + speech_args[SPEECH_MESSAGE] = message + //turfs /turf/open/water/jungle @@ -41,4 +101,9 @@ rare_pet_monkey_names = list("Sun Mukong", "Monkey Kong") /mob/living/simple_animal/hostile/jungle/leaper/boss - health = 450 + health = 550 + name = "Froggerosa" + +/mob/living/simple_animal/hostile/gorilla/jungle + tame = 1 + faction = list("neutral") diff --git a/code/modules/awaymissions/mission_code/wildwest.dm b/code/modules/awaymissions/mission_code/wildwest.dm index f13341faf3..bc7e2cbd08 100644 --- a/code/modules/awaymissions/mission_code/wildwest.dm +++ b/code/modules/awaymissions/mission_code/wildwest.dm @@ -132,6 +132,7 @@ var/triggered = 0 /obj/effect/meatgrinder/Crossed(atom/movable/AM) + . = ..() Bumped(AM) /obj/effect/meatgrinder/Bumped(atom/movable/AM) diff --git a/code/modules/awaymissions/pamphlet.dm b/code/modules/awaymissions/pamphlet.dm index 74bcb4b302..8da638f400 100644 --- a/code/modules/awaymissions/pamphlet.dm +++ b/code/modules/awaymissions/pamphlet.dm @@ -2,6 +2,11 @@ name = "pamphlet" icon_state = "pamphlet" +/obj/item/paper/pamphlet/violent_video_games + name = "pamphlet - \'Violent Video Games and You\'" + desc = "A pamphlet encouraging the reader to maintain a balanced lifestyle and take care of their mental health, while still enjoying video games in a healthy way. You probably don't need this..." + info = "They don't make you kill people. There, we said it. Now get back to work!" + /obj/item/paper/pamphlet/gateway info = "Welcome to the Nanotrasen Gateway project...
    \ Congratulations! If you're reading this, you and your superiors have decided that you're \ diff --git a/code/modules/awaymissions/super_secret_room.dm b/code/modules/awaymissions/super_secret_room.dm index 0bc0abef1e..af801e1e42 100644 --- a/code/modules/awaymissions/super_secret_room.dm +++ b/code/modules/awaymissions/super_secret_room.dm @@ -80,7 +80,7 @@ if(1000) SpeakPeace(list("The ends exists somewhere beyond meaningful milestones.", "There will be no more messages until then.", "You disgust me.")) if(5643) - SSmedals.UnlockMedal(MEDAL_TIMEWASTE, user.client) + user.client.give_award(/datum/award/achievement/misc/time_waste, user) var/obj/item/reagent_containers/food/drinks/trophy/gold_cup/never_ends = new(get_turf(user)) never_ends.name = "Overextending The Joke: First Place" never_ends.desc = "And so we are left alone with our regrets." diff --git a/code/modules/bsql/LICENSE b/code/modules/bsql/LICENSE deleted file mode 100644 index 2bee290914..0000000000 --- a/code/modules/bsql/LICENSE +++ /dev/null @@ -1,7 +0,0 @@ -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. \ No newline at end of file diff --git a/code/modules/bsql/core/connection.dm b/code/modules/bsql/core/connection.dm deleted file mode 100644 index fb8f729390..0000000000 --- a/code/modules/bsql/core/connection.dm +++ /dev/null @@ -1,68 +0,0 @@ -/datum/BSQL_Connection - var/id - var/connection_type - -BSQL_PROTECT_DATUM(/datum/BSQL_Connection) - -/datum/BSQL_Connection/New(connection_type, asyncTimeout, blockingTimeout, threadLimit) - if(asyncTimeout == null) - asyncTimeout = BSQL_DEFAULT_TIMEOUT - if(blockingTimeout == null) - blockingTimeout = asyncTimeout - if(threadLimit == null) - threadLimit = BSQL_DEFAULT_THREAD_LIMIT - - src.connection_type = connection_type - - world._BSQL_InitCheck(src) - - var/error = world._BSQL_Internal_Call("CreateConnection", connection_type, "[asyncTimeout]", "[blockingTimeout]", "[threadLimit]") - if(error) - BSQL_ERROR(error) - return - - id = world._BSQL_Internal_Call("GetConnection") - if(!id) - BSQL_ERROR("BSQL library failed to provide connect operation for connection id [id]([connection_type])!") - -BSQL_DEL_PROC(/datum/BSQL_Connection) - var/error - if(id) - error = world._BSQL_Internal_Call("ReleaseConnection", id) - . = ..() - if(error) - BSQL_ERROR(error) - -/datum/BSQL_Connection/BeginConnect(ipaddress, port, username, password, database) - var/error = world._BSQL_Internal_Call("OpenConnection", id, ipaddress, "[port]", username, password, database) - if(error) - BSQL_ERROR(error) - return - - var/op_id = world._BSQL_Internal_Call("GetOperation") - if(!op_id) - BSQL_ERROR("Library failed to provide connect operation for connection id [id]([connection_type])!") - return - - return new /datum/BSQL_Operation(src, op_id) - - -/datum/BSQL_Connection/BeginQuery(query) - var/error = world._BSQL_Internal_Call("NewQuery", id, query) - if(error) - BSQL_ERROR(error) - return - - var/op_id = world._BSQL_Internal_Call("GetOperation") - if(!op_id) - BSQL_ERROR("Library failed to provide query operation for connection id [id]([connection_type])!") - return - - return new /datum/BSQL_Operation/Query(src, op_id) - -/datum/BSQL_Connection/Quote(str) - if(!str) - return null; - . = world._BSQL_Internal_Call("QuoteString", id, "[str]") - if(!.) - BSQL_ERROR("Library failed to provide quote for [str]!") diff --git a/code/modules/bsql/core/library.dm b/code/modules/bsql/core/library.dm deleted file mode 100644 index 9b58ba314b..0000000000 --- a/code/modules/bsql/core/library.dm +++ /dev/null @@ -1,43 +0,0 @@ -/world/proc/_BSQL_Internal_Call(func, ...) - var/list/call_args = args.Copy(2) - BSQL_Debug("_BSQL_Internal_Call(): [args[1]]([call_args.Join(", ")])") - . = call(_BSQL_Library_Path(), func)(arglist(call_args)) - BSQL_Debug("Result: [. == null ? "NULL" : "\"[.]\""]") - -/world/proc/_BSQL_Library_Path() - return system_type == MS_WINDOWS ? "BSQL.dll" : "libBSQL.so" - -/world/proc/_BSQL_InitCheck(datum/BSQL_Connection/caller) - var/static/library_initialized = FALSE - if(_BSQL_Initialized()) - return - var/libPath = _BSQL_Library_Path() - if(!fexists(libPath)) - BSQL_DEL_CALL(caller) - BSQL_ERROR("Could not find [libPath]!") - return - - var/version = _BSQL_Internal_Call("Version") - if(version != BSQL_VERSION) - BSQL_DEL_CALL(caller) - BSQL_ERROR("BSQL DMAPI version mismatch! Expected [BSQL_VERSION], got [version == null ? "NULL" : version]!") - return - - var/result = _BSQL_Internal_Call("Initialize") - if(result) - BSQL_DEL_CALL(caller) - BSQL_ERROR(result) - return - _BSQL_Initialized(TRUE) - -/world/proc/_BSQL_Initialized(new_val) - var/static/bsql_library_initialized = FALSE - if(new_val != null) - bsql_library_initialized = new_val - return bsql_library_initialized - -/world/BSQL_Shutdown() - if(!_BSQL_Initialized()) - return - _BSQL_Internal_Call("Shutdown") - _BSQL_Initialized(FALSE) diff --git a/code/modules/bsql/core/operation.dm b/code/modules/bsql/core/operation.dm deleted file mode 100644 index a2cdbbe1ee..0000000000 --- a/code/modules/bsql/core/operation.dm +++ /dev/null @@ -1,47 +0,0 @@ -/datum/BSQL_Operation - var/datum/BSQL_Connection/connection - var/id - -BSQL_PROTECT_DATUM(/datum/BSQL_Operation) - -/datum/BSQL_Operation/New(datum/BSQL_Connection/connection, id) - src.connection = connection - src.id = id - -BSQL_DEL_PROC(/datum/BSQL_Operation) - var/error - if(!BSQL_IS_DELETED(connection)) - error = world._BSQL_Internal_Call("ReleaseOperation", connection.id, id) - . = ..() - if(error) - BSQL_ERROR(error) - -/datum/BSQL_Operation/IsComplete() - if(BSQL_IS_DELETED(connection)) - return TRUE - var/result = world._BSQL_Internal_Call("OpComplete", connection.id, id) - if(!result) - BSQL_ERROR("Error fetching operation [id] for connection [connection.id]!") - return - return result == "DONE" - -/datum/BSQL_Operation/GetError() - if(BSQL_IS_DELETED(connection)) - return "Connection deleted!" - return world._BSQL_Internal_Call("GetError", connection.id, id) - -/datum/BSQL_Operation/GetErrorCode() - if(BSQL_IS_DELETED(connection)) - return -2 - return text2num(world._BSQL_Internal_Call("GetErrorCode", connection.id, id)) - -/datum/BSQL_Operation/WaitForCompletion() - if(BSQL_IS_DELETED(connection)) - return - var/error = world._BSQL_Internal_Call("BlockOnOperation", connection.id, id) - if(error) - if(error == "Operation timed out!") //match this with the implementation - return FALSE - BSQL_ERROR("Error waiting for operation [id] for connection [connection.id]! [error]") - return - return TRUE diff --git a/code/modules/bsql/core/query.dm b/code/modules/bsql/core/query.dm deleted file mode 100644 index fc09fb06b0..0000000000 --- a/code/modules/bsql/core/query.dm +++ /dev/null @@ -1,35 +0,0 @@ -/datum/BSQL_Operation/Query - var/last_result_json - var/list/last_result - -BSQL_PROTECT_DATUM(/datum/BSQL_Operation/Query) - -/datum/BSQL_Operation/Query/CurrentRow() - return last_result - -/datum/BSQL_Operation/Query/IsComplete() - //whole different ballgame here - if(BSQL_IS_DELETED(connection)) - return TRUE - var/result = world._BSQL_Internal_Call("ReadyRow", connection.id, id) - switch(result) - if("DONE") - //load the data - LoadQueryResult() - return TRUE - if("NOTDONE") - return FALSE - else - BSQL_ERROR(result) - -/datum/BSQL_Operation/Query/WaitForCompletion() - . = ..() - if(.) - LoadQueryResult() - -/datum/BSQL_Operation/Query/proc/LoadQueryResult() - last_result_json = world._BSQL_Internal_Call("GetRow", connection.id, id) - if(last_result_json) - last_result = json_decode(last_result_json) - else - last_result = null diff --git a/code/modules/bsql/includes.dm b/code/modules/bsql/includes.dm deleted file mode 100644 index d05dcb6451..0000000000 --- a/code/modules/bsql/includes.dm +++ /dev/null @@ -1,4 +0,0 @@ -#include "core\connection.dm" -#include "core\library.dm" -#include "core\operation.dm" -#include "core\query.dm" diff --git a/code/modules/cargo/bounties/reagent.dm b/code/modules/cargo/bounties/reagent.dm index 391b4ff464..57501beab3 100644 --- a/code/modules/cargo/bounties/reagent.dm +++ b/code/modules/cargo/bounties/reagent.dm @@ -35,7 +35,7 @@ name = "Simple Drink" reward = 1500 -datum/bounty/reagent/simple_drink/New() +/datum/bounty/reagent/simple_drink/New() // Don't worry about making this comprehensive. It doesn't matter if some drinks are skipped. var/static/list/possible_reagents = list(\ /datum/reagent/consumable/ethanol/antifreeze,\ @@ -91,7 +91,7 @@ datum/bounty/reagent/simple_drink/New() name = "Complex Drink" reward = 4000 -datum/bounty/reagent/complex_drink/New() +/datum/bounty/reagent/complex_drink/New() // Don't worry about making this comprehensive. It doesn't matter if some drinks are skipped. var/static/list/possible_reagents = list(\ /datum/reagent/consumable/ethanol/atomicbomb,\ @@ -124,7 +124,7 @@ datum/bounty/reagent/complex_drink/New() reward = 2750 required_volume = 30 -datum/bounty/reagent/chemical/New() +/datum/bounty/reagent/chemical/New() // Don't worry about making this comprehensive. It doesn't matter if some chems are skipped. var/static/list/possible_reagents = list(\ /datum/reagent/medicine/leporazine,\ diff --git a/code/modules/cargo/centcom_podlauncher.dm b/code/modules/cargo/centcom_podlauncher.dm index 77fbd6c6bd..05a9eef4f2 100644 --- a/code/modules/cargo/centcom_podlauncher.dm +++ b/code/modules/cargo/centcom_podlauncher.dm @@ -20,8 +20,7 @@ set name = "Config/Launch Supplypod" set desc = "Configure and launch a CentCom supplypod full of whatever your heart desires!" set category = "Admin.Events" - var/datum/centcom_podlauncher/plaunch = new(usr)//create the datum - plaunch.ui_interact(usr)//datum has a tgui component, here we open the window + new /datum/centcom_podlauncher(usr)//create the datum //Variables declared to change how items in the launch bay are picked and launched. (Almost) all of these are changed in the ui_act proc //Some effect groups are choices, while other are booleans. This is because some effects can stack, while others dont (ex: you can stack explosion and quiet, but you cant stack ordered launch and random launch) @@ -56,7 +55,6 @@ var/list/cam_plane_masters var/obj/screen/background/cam_background var/tabIndex = 1 - var/list/timers = list("landingDelay", "fallDuration", "openingDelay", "departureDelay") var/renderLighting = FALSE /datum/centcom_podlauncher/New(user) //user can either be a client or a mob @@ -148,10 +146,9 @@ data["launchChoice"] = launchChoice //Launch turfs all at once (0), ordered (1), or randomly(1) data["explosionChoice"] = explosionChoice //An explosion that occurs when landing. Can be no explosion (0), custom explosion (1), or maxcap (2) data["damageChoice"] = damageChoice //Damage that occurs to any mob under the pod when it lands. Can be no damage (0), custom damage (1), or gib+5000dmg (2) - data["delay_1"] = temp_pod.landingDelay //How long the pod takes to land after launching - data["delay_2"] = temp_pod.fallDuration //How long the pod's falling animation lasts - data["delay_3"] = temp_pod.openingDelay //How long the pod takes to open after landing - data["delay_4"] = temp_pod.departureDelay //How long the pod takes to leave after opening (if bluespace=true, it deletes. if reversing=true, it flies back to centcom) + data["delays"] = temp_pod.delays + data["rev_delays"] = temp_pod.reverse_delays + data["custom_rev_delay"] = temp_pod.custom_rev_delay data["styleChoice"] = temp_pod.style //Style is a variable that keeps track of what the pod is supposed to look like. It acts as an index to the GLOB.podstyles list in cargo.dm defines to get the proper icon/name/desc for the pod. data["effectShrapnel"] = temp_pod.effectShrapnel //If true, creates a cloud of shrapnel of a decided type and magnitude on landing data["shrapnelType"] = "[temp_pod.shrapnel_type]" //Path2String @@ -166,7 +163,7 @@ data["effectCircle"] = temp_pod.effectCircle //If true, allows the pod to come in at any angle. Bit of a weird feature but whatever its here data["effectBurst"] = effectBurst //IOf true, launches five pods at once (with a very small delay between for added coolness), in a 3x3 area centered around the area data["effectReverse"] = temp_pod.reversing //If true, the pod will not send any items. Instead, after opening, it will close again (picking up items/mobs) and fly back to centcom - data["reverseOptionList"] = temp_pod.reverseOptionList + data["reverse_option_list"] = temp_pod.reverse_option_list data["effectTarget"] = specificTarget //Launches the pod at the turf of a specific mob target, rather than wherever the user clicked. Useful for smites data["effectName"] = temp_pod.adminNamed //Determines whether or not the pod has been named by an admin. If true, the pod's name will not get overridden when the style of the pod changes (changing the style of the pod normally also changes the name+desc) data["podName"] = temp_pod.name @@ -182,7 +179,8 @@ return data /datum/centcom_podlauncher/ui_act(action, params) - if(..()) + . = ..() + if(.) return switch(action) ////////////////////////////UTILITIES////////////////// @@ -398,7 +396,7 @@ . = TRUE if("reverseOption") var/reverseOption = params["reverseOption"] - temp_pod.reverseOptionList[reverseOption] = !temp_pod.reverseOptionList[reverseOption] + temp_pod.reverse_option_list[reverseOption] = !temp_pod.reverse_option_list[reverseOption] . = TRUE if("effectTarget") //Toggle: Launch at a specific mob (instead of at whatever turf you click on). Used for the supplypod smite if (specificTarget) @@ -415,13 +413,19 @@ ////////////////////////////TIMER DELAYS////////////////// if("editTiming") //Change the different timers relating to the pod var/delay = params["timer"] - var/timer = timers[delay] var/value = params["value"] - temp_pod.vars[timer] = value * 10 + var/reverse = params["reverse"] + if (reverse) + temp_pod.reverse_delays[delay] = value * 10 + else + temp_pod.delays[delay] = value * 10 . = TRUE if("resetTiming") - for (var/timer in timers) - temp_pod.vars[timer] = initial(temp_pod.vars[timer]) + temp_pod.delays = list(POD_TRANSIT = 20, POD_FALLING = 4, POD_OPENING = 30, POD_LEAVING = 30) + temp_pod.reverse_delays = list(POD_TRANSIT = 20, POD_FALLING = 4, POD_OPENING = 30, POD_LEAVING = 30) + . = TRUE + if("toggleRevDelays") + temp_pod.custom_rev_delay = !temp_pod.custom_rev_delay . = TRUE ////////////////////////////ADMIN SOUNDS////////////////// if("fallingSound") //Admin sound from a local file that plays when the pod lands @@ -544,7 +548,7 @@ var/turf/drop = locate(coords_list[1], coords_list[2], coords_list[3]) setupView(RANGE_TURFS(3, drop)) -/datum/centcom_podlauncher/proc/setupView(var/list/visible_turfs) +/datum/centcom_podlauncher/proc/setupView(list/visible_turfs) var/list/bbox = get_bbox_of_atoms(visible_turfs) var/size_x = bbox[3] - bbox[1] + 1 var/size_y = bbox[4] - bbox[2] + 1 @@ -553,7 +557,7 @@ cam_background.icon_state = "clear" cam_background.fill_rect(1, 1, size_x, size_y) -/datum/centcom_podlauncher/proc/updateCursor(var/forceClear = FALSE) //Update the mouse of the user +/datum/centcom_podlauncher/proc/updateCursor(forceClear = FALSE) //Update the mouse of the user if (!holder) //Can't update the mouse icon if the client doesnt exist! return if (!forceClear && (launcherActivated || picking_dropoff_turf)) //If the launching param is true, we give the user new mouse icons. @@ -702,11 +706,10 @@ /datum/centcom_podlauncher/proc/launch(turf/target_turf) //Game time started if (isnull(target_turf)) return - var/obj/structure/closet/supplypod/centcompod/toLaunch = DuplicateObject(temp_pod, sameloc = TRUE) //Duplicate the temp_pod (which we have been varediting or configuring with the UI) and store the result + var/obj/structure/closet/supplypod/centcompod/toLaunch = DuplicateObject(temp_pod) //Duplicate the temp_pod (which we have been varediting or configuring with the UI) and store the result toLaunch.update_icon()//we update_icon() here so that the door doesnt "flicker on" right after it lands - //We don't have this area, lets just have it where we had the temp pod - //var/shippingLane = GLOB.areas_by_type[/area/centcom/supplypod/supplypod_temp_holding] - //toLaunch.forceMove(shippingLane) + var/shippingLane = GLOB.areas_by_type[/area/centcom/supplypod/supplypod_temp_holding] + toLaunch.forceMove(shippingLane) if (launchClone) //We arent launching the actual items from the bay, rather we are creating clones and launching those if(launchRandomItem) var/launch_candidate = pick_n_take(launchList) @@ -792,7 +795,7 @@ for (var/mob/living/M in whoDyin) admin_ticket_log(M, "[key_name_admin(usr)] [msg]") -/datum/centcom_podlauncher/proc/loadData(var/list/dataToLoad) +/datum/centcom_podlauncher/proc/loadData(list/dataToLoad) bayNumber = dataToLoad["bayNumber"] customDropoff = dataToLoad["customDropoff"] renderLighting = dataToLoad["renderLighting"] @@ -801,10 +804,9 @@ launchChoice = dataToLoad["launchChoice"] //Launch turfs all at once (0), ordered (1), or randomly(1) explosionChoice = dataToLoad["explosionChoice"] //An explosion that occurs when landing. Can be no explosion (0), custom explosion (1), or maxcap (2) damageChoice = dataToLoad["damageChoice"] //Damage that occurs to any mob under the pod when it lands. Can be no damage (0), custom damage (1), or gib+5000dmg (2) - temp_pod.landingDelay = dataToLoad["delay_1"] //How long the pod takes to land after launching - temp_pod.fallDuration = dataToLoad["delay_2"] //How long the pod's falling animation lasts - temp_pod.openingDelay = dataToLoad["delay_3"] //How long the pod takes to open after landing - temp_pod.departureDelay = dataToLoad["delay_4"] //How long the pod takes to leave after opening (if bluespace=true, it deletes. if reversing=true, it flies back to centcom) + temp_pod.delays = dataToLoad["delays"] + temp_pod.reverse_delays = dataToLoad["rev_delays"] + temp_pod.custom_rev_delay = dataToLoad["custom_rev_delay"] temp_pod.setStyle(dataToLoad["styleChoice"]) //Style is a variable that keeps track of what the pod is supposed to look like. It acts as an index to the GLOB.podstyles list in cargo.dm defines to get the proper icon/name/desc for the pod. temp_pod.effectShrapnel = dataToLoad["effectShrapnel"] //If true, creates a cloud of shrapnel of a decided type and magnitude on landing temp_pod.shrapnel_type = text2path(dataToLoad["shrapnelType"]) @@ -819,7 +821,7 @@ temp_pod.effectCircle = dataToLoad["effectCircle"] //If true, allows the pod to come in at any angle. Bit of a weird feature but whatever its here effectBurst = dataToLoad["effectBurst"] //IOf true, launches five pods at once (with a very small delay between for added coolness), in a 3x3 area centered around the area temp_pod.reversing = dataToLoad["effectReverse"] //If true, the pod will not send any items. Instead, after opening, it will close again (picking up items/mobs) and fly back to centcom - temp_pod.reverseOptionList = dataToLoad["reverseOptionList"] + temp_pod.reverse_option_list = dataToLoad["reverse_option_list"] specificTarget = dataToLoad["effectTarget"] //Launches the pod at the turf of a specific mob target, rather than wherever the user clicked. Useful for smites temp_pod.adminNamed = dataToLoad["effectName"] //Determines whether or not the pod has been named by an admin. If true, the pod's name will not get overridden when the style of the pod changes (changing the style of the pod normally also changes the name+desc) temp_pod.name = dataToLoad["podName"] diff --git a/code/modules/cargo/console.dm b/code/modules/cargo/console.dm index 5a976b0abb..9801b165c7 100644 --- a/code/modules/cargo/console.dm +++ b/code/modules/cargo/console.dm @@ -3,8 +3,14 @@ desc = "Used to order supplies, approve requests, and control the shuttle." icon_screen = "supply" circuit = /obj/item/circuitboard/computer/cargo + light_color = "#E2853D"//orange + ///Can the supply console send the shuttle back and forth? Used in the UI backend. + var/can_send = TRUE + ///Can this console only send requests? var/requestonly = FALSE + ///Can you approve requests placed for cargo? Works differently between the app and the computer. + var/can_approve_requests = TRUE var/contraband = FALSE var/self_paid = FALSE var/safety_warning = "For safety reasons, the automated supply shuttle \ @@ -16,25 +22,21 @@ /// var that tracks message cooldown var/message_cooldown var/list/loaded_coupons - - light_color = "#E2853D"//orange + /// var that makes express console use rockets + var/is_express = FALSE /obj/machinery/computer/cargo/request name = "supply request console" desc = "Used to request supplies from cargo." icon_screen = "request" circuit = /obj/item/circuitboard/computer/cargo/request + can_send = FALSE + can_approve_requests = FALSE requestonly = TRUE /obj/machinery/computer/cargo/Initialize() . = ..() radio = new /obj/item/radio/headset/headset_cargo(src) - var/obj/item/circuitboard/computer/cargo/board = circuit - contraband = board.contraband - if (board.obj_flags & EMAGGED) - obj_flags |= EMAGGED - else - obj_flags &= ~EMAGGED /obj/machinery/computer/cargo/Destroy() QDEL_NULL(radio) @@ -64,6 +66,10 @@ board.obj_flags |= EMAGGED update_static_data(user) +/obj/machinery/computer/cargo/on_construction() + . = ..() + circuit.configure_machine(src) + /obj/machinery/computer/cargo/ui_interact(mob/user, datum/tgui/ui) ui = SStgui.try_update_ui(user, src, ui) if(!ui) @@ -81,6 +87,8 @@ data["docked"] = SSshuttle.supply.mode == SHUTTLE_IDLE data["loan"] = !!SSshuttle.shuttle_loan data["loan_dispatched"] = SSshuttle.shuttle_loan && SSshuttle.shuttle_loan.dispatched + data["can_send"] = can_send + data["can_approve_requests"] = can_approve_requests var/message = "Remember to stamp and send back the supply manifests." if(SSshuttle.centcom_message) message = SSshuttle.centcom_message @@ -128,14 +136,15 @@ "id" = pack, "desc" = P.desc || P.name, // If there is a description, use it. Otherwise use the pack's name. "goody" = P.goody, - "private_goody" = P.goody == PACK_GOODY_PRIVATE, "access" = P.access, + "private_goody" = P.goody == PACK_GOODY_PRIVATE, "can_private_buy" = P.can_private_buy )) return data /obj/machinery/computer/cargo/ui_act(action, params, datum/tgui/ui) - if(..()) + . = ..() + if(.) return switch(action) if("send") @@ -147,13 +156,13 @@ return if(SSshuttle.supply.getDockedId() == "supply_home") SSshuttle.supply.export_categories = get_export_categories() - SSshuttle.moveShuttle("supply", "supply_away", TRUE) + SSshuttle.moveShuttle(SSshuttle.supply.id, "supply_away", TRUE) say("The supply shuttle is departing.") investigate_log("[key_name(usr)] sent the supply shuttle away.", INVESTIGATE_CARGO) else investigate_log("[key_name(usr)] called the supply shuttle.", INVESTIGATE_CARGO) say("The supply shuttle has been called and will arrive in [SSshuttle.supply.timeLeft(600)] minutes.") - SSshuttle.moveShuttle("supply", "supply_home", TRUE) + SSshuttle.moveShuttle(SSshuttle.supply.id, "supply_home", TRUE) . = TRUE if("loan") if(!SSshuttle.shuttle_loan) @@ -172,6 +181,8 @@ log_game("[key_name(usr)] accepted a shuttle loan event.") . = TRUE if("add") + if(is_express) + return var/id = text2path(params["id"]) var/datum/supply_pack/pack = SSshuttle.supply_packs[id] if(!istype(pack)) @@ -195,9 +206,9 @@ rank = "Silicon" var/datum/bank_account/account - if(self_paid && ishuman(usr)) - var/mob/living/carbon/human/H = usr - var/obj/item/card/id/id_card = H.get_idcard(TRUE) + if(self_paid && isliving(usr)) + var/mob/living/L = usr + var/obj/item/card/id/id_card = L.get_idcard(TRUE) if(!istype(id_card)) say("No ID card detected.") return diff --git a/code/modules/cargo/exports/gear.dm b/code/modules/cargo/exports/gear.dm index 678948128f..8e93fc5fc3 100644 --- a/code/modules/cargo/exports/gear.dm +++ b/code/modules/cargo/exports/gear.dm @@ -473,7 +473,7 @@ //Soft Suits //Blanket -datum/export/gear/space/helmet +/datum/export/gear/space/helmet cost = 55 unit_name = "space helmet" export_types = list(/obj/item/clothing/head/helmet/space) @@ -485,7 +485,7 @@ datum/export/gear/space/helmet export_types = list(/obj/item/clothing/suit/space) include_subtypes = TRUE -datum/export/gear/space/helmet/plasma +/datum/export/gear/space/helmet/plasma cost = 100 unit_name = "plasmaman space helmet" export_types = list(/obj/item/clothing/suit/space/eva/plasmaman) @@ -495,7 +495,7 @@ datum/export/gear/space/helmet/plasma unit_name = "plasmaman space suit" export_types = list(/obj/item/clothing/suit/space/eva/plasmaman) -datum/export/gear/space/helmet/synda +/datum/export/gear/space/helmet/synda cost = 150 //Flash proof unit_name = "syndicate space helmet" export_types = list(/obj/item/clothing/head/helmet/space/syndicate) @@ -510,7 +510,7 @@ datum/export/gear/space/helmet/synda //Glasses //Blanket -datum/export/gear/glasses //glasses are not worth selling +/datum/export/gear/glasses //glasses are not worth selling cost = 3 unit_name = "glasses" export_types = list(/obj/item/clothing/glasses) diff --git a/code/modules/cargo/packs/costumes_toys.dm b/code/modules/cargo/packs/costumes_toys.dm index 08f9a927c6..6a37ef80a9 100644 --- a/code/modules/cargo/packs/costumes_toys.dm +++ b/code/modules/cargo/packs/costumes_toys.dm @@ -318,7 +318,7 @@ /obj/item/clothing/under/suit/white, // white is a weird color for a groom but some people are weird /obj/item/clothing/under/suit/polychromic, /obj/item/clothing/under/suit/polychromic, // in case you can't be satisfied with the most fitting choices, of course. - /obj/item/clothing/under/dress/wedding, + /obj/item/clothing/under/dress/wedding, /obj/item/clothing/under/dress/wedding, // this is what you actually bought the crate for. You can't get these anywhere else. /obj/item/clothing/under/dress/wedding/orange, /obj/item/clothing/under/dress/wedding/orange, @@ -333,4 +333,22 @@ /obj/item/storage/fancy/ringbox/silver, /obj/item/storage/fancy/ringbox/silver) //diamond rings cost the same price as this crate via cargo so we're not giving you two for free. Wedding rings are traditionally less valuable anyway. crate_name = "wedding crate" - + +/datum/supply_pack/costumes_toys/randomised/tcg + name = "Big-Ass Booster Pack Pack" + desc = "A bumper load of NT TCG Booster Packs of varying series. Collect them all!" + cost = 3000 + contains = list() + crate_name = "booster pack pack" + +/datum/supply_pack/costumes_toys/randomised/tcg/generate() + . = ..() + var/list/cardtypes = subtypesof(/obj/item/cardpack) + for(var/cardtype in cardtypes) + var/obj/item/cardpack/pack = new cardtype(.) + if(pack.illegal) + cardtypes.Remove(cardtype) + qdel(pack) + for(var/i in 1 to 10) + var/cardpacktype = pick(cardtypes) + new cardpacktype(.) diff --git a/code/modules/cargo/packs/misc.dm b/code/modules/cargo/packs/misc.dm index 9c15e75cd6..a158f0f1c1 100644 --- a/code/modules/cargo/packs/misc.dm +++ b/code/modules/cargo/packs/misc.dm @@ -42,18 +42,21 @@ /datum/supply_pack/misc/book_crate name = "Book Crate" - desc = "Surplus from the Nanotrasen Archives, these five books are sure to be good reads." + desc = "Surplus from the Nanotrasen Archives, these seven books are sure to be good reads." + // cost = CARGO_CRATE_VALUE * 3 cost = 1500 contains = list(/obj/item/book/codex_gigas, /obj/item/book/manual/random/, /obj/item/book/manual/random/, /obj/item/book/manual/random/, - /obj/item/book/random/triple) + /obj/item/book/random, + /obj/item/book/random, + /obj/item/book/random) crate_type = /obj/structure/closet/crate/wooden /datum/supply_pack/misc/paper name = "Bureaucracy Crate" - desc = "High stacks of papers on your desk Are a big problem - make it Pea-sized with these bureaucratic supplies! Contains five pens, some camera film, hand labeler supplies, a paper bin, three folders, two clipboards and two stamps as well as a briefcase."//that was too forced + desc = "High stacks of papers on your desk Are a big problem - make it Pea-sized with these bureaucratic supplies! Contains six pens, some camera film, hand labeler supplies, a paper bin, a carbon paper bin, three folders, a laser pointer, two clipboards and two stamps."//that was too forced cost = 1500 contains = list(/obj/structure/filingcabinet/chestdrawer/wheeled, /obj/item/camera_film, @@ -61,9 +64,11 @@ /obj/item/hand_labeler_refill, /obj/item/hand_labeler_refill, /obj/item/paper_bin, + /obj/item/paper_bin/carbon, /obj/item/pen/fourcolor, /obj/item/pen/fourcolor, /obj/item/pen, + /obj/item/pen/fountain, /obj/item/pen/blue, /obj/item/pen/red, /obj/item/folder/blue, @@ -73,7 +78,7 @@ /obj/item/clipboard, /obj/item/stamp, /obj/item/stamp/denied, - /obj/item/storage/briefcase) + /obj/item/laser_pointer/purple) crate_name = "bureaucracy crate" /datum/supply_pack/misc/captain_pen @@ -94,6 +99,30 @@ crate_type = /obj/structure/closet/crate/wooden crate_name = "calligraphy crate" +/datum/supply_pack/misc/toner + name = "Toner Crate" + desc = "Spent too much ink printing butt pictures? Fret not, with these six toner refills, you'll be printing butts 'till the cows come home!'" + cost = 200 * 4 + contains = list(/obj/item/toner, + /obj/item/toner, + /obj/item/toner, + /obj/item/toner, + /obj/item/toner, + /obj/item/toner) + crate_name = "toner crate" + +/datum/supply_pack/misc/toner_large + name = "Toner Crate (Large)" + desc = "Tired of changing toner cartridges? These six extra heavy duty refills contain roughly five times as much toner as the base model!" + cost = 200 * 6 + contains = list(/obj/item/toner/large, + /obj/item/toner/large, + /obj/item/toner/large, + /obj/item/toner/large, + /obj/item/toner/large, + /obj/item/toner/large) + crate_name = "large toner crate" + ////////////////////////////////////////////////////////////////////////////// //////////////////////////////// Entertainment /////////////////////////////// ////////////////////////////////////////////////////////////////////////////// diff --git a/code/modules/cargo/packs/vending.dm b/code/modules/cargo/packs/vending.dm index 810cfd8d6e..e55f24d91e 100644 --- a/code/modules/cargo/packs/vending.dm +++ b/code/modules/cargo/packs/vending.dm @@ -79,7 +79,7 @@ desc = "A fun way to spend the shift. Contains unmentionable desires." cost = 2000 contraband = TRUE - contains = list(/obj/item/vending_refill/kink, /obj/item/circuitboard/machine/kinkmate) + contains = list(/obj/item/vending_refill/kink) crate_name = "Kinkmate construction kit" /datum/supply_pack/vending/medical diff --git a/code/modules/cargo/supplypod.dm b/code/modules/cargo/supplypod.dm index efac62c930..90adf8e7ff 100644 --- a/code/modules/cargo/supplypod.dm +++ b/code/modules/cargo/supplypod.dm @@ -23,9 +23,9 @@ //*****NOTE*****: Many of these comments are similarly described in centcom_podlauncher.dm. If you change them here, please consider doing so in the centcom podlauncher code as well! var/adminNamed = FALSE //Determines whether or not the pod has been named by an admin. If true, the pod's name will not get overridden when the style of the pod changes (changing the style of the pod normally also changes the name+desc) var/bluespace = FALSE //If true, the pod deletes (in a shower of sparks) after landing - var/landingDelay = 30 //How long the pod takes to land after launching - var/openingDelay = 30 //How long the pod takes to open after landing - var/departureDelay = 30 //How long the pod takes to leave after opening. If bluespace = TRUE, it deletes. If reversing = TRUE, it flies back to centcom. + var/delays = list(POD_TRANSIT = 30, POD_FALLING = 4, POD_OPENING = 30, POD_LEAVING = 30) + var/reverse_delays = list(POD_TRANSIT = 30, POD_FALLING = 4, POD_OPENING = 30, POD_LEAVING = 30) + var/custom_rev_delay = FALSE var/damage = 0 //Damage that occurs to any mob under the pod when it lands. var/effectStun = FALSE //If true, stuns anyone under the pod when it launches until it lands, forcing them to get hit by the pod. Devilish! var/effectLimb = FALSE //If true, pops off a limb (if applicable) from anyone caught under the pod when it lands @@ -38,7 +38,6 @@ var/style = STYLE_STANDARD //Style is a variable that keeps track of what the pod is supposed to look like. It acts as an index to the GLOB.podstyles list in cargo.dm defines to get the proper icon/name/desc for the pod. var/reversing = FALSE //If true, the pod will not send any items. Instead, after opening, it will close again (picking up items/mobs) and fly back to centcom var/list/reverse_dropoff_coords //Turf that the reverse pod will drop off it's newly-acquired cargo to - var/fallDuration = 4 var/fallingSoundLength = 11 var/fallingSound = 'sound/weapons/mortar_long_whistle.ogg'//Admin sound to play before the pod lands var/landingSound //Admin sound to play when the pod lands @@ -57,14 +56,13 @@ var/effectShrapnel = FALSE var/shrapnel_type = /obj/item/projectile/bullet/shrapnel var/shrapnel_magnitude = 3 - var/list/reverseOptionList = list("Mobs"=FALSE,"Objects"=FALSE,"Anchored"=FALSE,"Underfloor"=FALSE,"Wallmounted"=FALSE,"Floors"=FALSE,"Walls"=FALSE) + var/list/reverse_option_list = list("Mobs"=FALSE,"Objects"=FALSE,"Anchored"=FALSE,"Underfloor"=FALSE,"Wallmounted"=FALSE,"Floors"=FALSE,"Walls"=FALSE, "Mecha"=FALSE) var/list/turfs_in_cargo = list() /obj/structure/closet/supplypod/bluespacepod style = STYLE_BLUESPACE bluespace = TRUE explosionSize = list(0,0,1,2) - landingDelay = 15 //Slightly quicker than the supplypod /obj/structure/closet/supplypod/extractionpod name = "Syndicate Extraction Pod" @@ -73,16 +71,16 @@ style = STYLE_SYNDICATE bluespace = TRUE explosionSize = list(0,0,1,2) - landingDelay = 25 //Longer than others + delays = list(POD_TRANSIT = 25, POD_FALLING = 4, POD_OPENING = 30, POD_LEAVING = 30) /obj/structure/closet/supplypod/centcompod style = STYLE_CENTCOM bluespace = TRUE explosionSize = list(0,0,0,0) - landingDelay = 20 //Very speedy! + delays = list(POD_TRANSIT = 20, POD_FALLING = 4, POD_OPENING = 30, POD_LEAVING = 30) resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF -/obj/structure/closet/supplypod/Initialize(var/customStyle = FALSE) +/obj/structure/closet/supplypod/Initialize(mapload, customStyle = FALSE) . = ..() if (!loc) var/shippingLane = GLOB.areas_by_type[/area/centcom/supplypod/supplypod_temp_holding] //temporary holder for supplypods mid-transit @@ -212,9 +210,8 @@ var/obj/error_landmark = locate(/obj/effect/landmark/error) in GLOB.landmarks_list var/turf/error_landmark_turf = get_turf(error_landmark) reverse_dropoff_coords = list(error_landmark_turf.x, error_landmark_turf.y, error_landmark_turf.z) - landingDelay = initial(landingDelay) //Reset the landing timers so we land on whatever turf we're aiming at normally. Will be changed to be editable later (tm) - fallDuration = initial(fallDuration) //This is so if someone adds a really long dramatic landing time they don't have to sit through it twice on the pod's return trip - openingDelay = initial(openingDelay) + if (custom_rev_delay) + delays = reverse_delays backToNonReverseIcon() var/turf/return_turf = locate(reverse_dropoff_coords[1], reverse_dropoff_coords[2], reverse_dropoff_coords[3]) new /obj/effect/pod_landingzone(return_turf, src) @@ -274,11 +271,11 @@ var/mob/living/simple_animal/pet/gondola/gondolapod/benis = new(turf_underneath, src) benis.contents |= contents //Move the contents of this supplypod into the gondolapod mob. moveToNullspace() - addtimer(CALLBACK(src, .proc/open_pod, benis), openingDelay) //After the openingDelay passes, we use the open proc from this supplyprod while referencing the contents of the "holder", in this case the gondolapod mob + addtimer(CALLBACK(src, .proc/open_pod, benis), delays[POD_OPENING]) //After the opening delay passes, we use the open proc from this supplyprod while referencing the contents of the "holder", in this case the gondolapod mob else if (style == STYLE_SEETHROUGH) open_pod(src) else - addtimer(CALLBACK(src, .proc/open_pod, src), openingDelay) //After the openingDelay passes, we use the open proc from this supplypod, while referencing this supplypod's contents + addtimer(CALLBACK(src, .proc/open_pod, src), delays[POD_OPENING]) //After the opening delay passes, we use the open proc from this supplypod, while referencing this supplypod's contents /obj/structure/closet/supplypod/proc/open_pod(atom/movable/holder, broken = FALSE, forced = FALSE) //The holder var represents an atom whose contents we will be working with if (!holder) @@ -306,9 +303,9 @@ startExitSequence(src) else if (reversing) - addtimer(CALLBACK(src, .proc/SetReverseIcon), departureDelay/2) //Finish up the pod's duties after a certain amount of time + addtimer(CALLBACK(src, .proc/SetReverseIcon), delays[POD_LEAVING]/2) //Finish up the pod's duties after a certain amount of time if(!stay_after_drop) // Departing should be handled manually - addtimer(CALLBACK(src, .proc/startExitSequence, holder), departureDelay*(4/5)) //Finish up the pod's duties after a certain amount of time + addtimer(CALLBACK(src, .proc/startExitSequence, holder), delays[POD_LEAVING]*(4/5)) //Finish up the pod's duties after a certain amount of time /obj/structure/closet/supplypod/proc/startExitSequence(atom/movable/holder) if (leavingSound) @@ -329,7 +326,7 @@ take_contents(holder) playsound(holder, close_sound, soundVolume*0.75, TRUE, -3) holder.setClosed() - addtimer(CALLBACK(src, .proc/preReturn, holder), departureDelay * 0.2) //Start to leave a bit after closing for cinematic effect + addtimer(CALLBACK(src, .proc/preReturn, holder), delays[POD_LEAVING] * 0.2) //Start to leave a bit after closing for cinematic effect /obj/structure/closet/supplypod/take_contents(atom/movable/holder) var/turf/turf_underneath = holder.drop_location() @@ -355,7 +352,7 @@ if(to_insert.invisibility == INVISIBILITY_ABSTRACT) return FALSE if(ismob(to_insert)) - if(!reverseOptionList["Mobs"]) + if(!reverse_option_list["Mobs"]) return FALSE if(!isliving(to_insert)) //let's not put ghosts or camera mobs inside return FALSE @@ -374,32 +371,30 @@ return FALSE if(istype(obj_to_insert, /obj/effect/supplypod_rubble)) return FALSE - if(obj_to_insert.level == 1) - return FALSE // underfloor, until we get hide components. -/* - if((obj_to_insert.comp_lookup && obj_to_insert.comp_lookup[COMSIG_OBJ_HIDE]) && reverseOptionList["Underfloor"]) + // if((obj_to_insert.comp_lookup && obj_to_insert.comp_lookup[COMSIG_OBJ_HIDE]) && reverse_option_list["Underfloor"]) + // return TRUE + // else if ((obj_to_insert.comp_lookup && obj_to_insert.comp_lookup[COMSIG_OBJ_HIDE]) && !reverse_option_list["Underfloor"]) + // return FALSE + if(isProbablyWallMounted(obj_to_insert) && reverse_option_list["Wallmounted"]) return TRUE - else if ((obj_to_insert.comp_lookup && obj_to_insert.comp_lookup[COMSIG_OBJ_HIDE]) && !reverseOptionList["Underfloor"]) + else if (isProbablyWallMounted(obj_to_insert) && !reverse_option_list["Wallmounted"]) return FALSE -*/ - if(isProbablyWallMounted(obj_to_insert) && reverseOptionList["Wallmounted"]) + if(!obj_to_insert.anchored && reverse_option_list["Unanchored"]) return TRUE - else if (isProbablyWallMounted(obj_to_insert) && !reverseOptionList["Wallmounted"]) - return FALSE - if(!obj_to_insert.anchored && reverseOptionList["Unanchored"]) + if(obj_to_insert.anchored && !ismecha(obj_to_insert) && reverse_option_list["Anchored"]) //Mecha are anchored but there is a separate option for them return TRUE - if(obj_to_insert.anchored && reverseOptionList["Anchored"]) + if(ismecha(obj_to_insert) && reverse_option_list["Mecha"]) return TRUE return FALSE else if (isturf(to_insert)) - if(isfloorturf(to_insert) && reverseOptionList["Floors"]) + if(isfloorturf(to_insert) && reverse_option_list["Floors"]) return TRUE - if(isfloorturf(to_insert) && !reverseOptionList["Floors"]) + if(isfloorturf(to_insert) && !reverse_option_list["Floors"]) return FALSE - if(isclosedturf(to_insert) && reverseOptionList["Walls"]) + if(isclosedturf(to_insert) && reverse_option_list["Walls"]) return TRUE - if(isclosedturf(to_insert) && !reverseOptionList["Walls"]) + if(isclosedturf(to_insert) && !reverse_option_list["Walls"]) return FALSE return FALSE return TRUE @@ -459,10 +454,12 @@ if(!glow_effect) return glow_effect.layer = LOW_ITEM_LAYER - glow_effect.fadeAway(openingDelay) + glow_effect.fadeAway(delays[POD_OPENING]) + glow_effect = null /obj/structure/closet/supplypod/Destroy() deleteRubble() + endGlow() open_pod(src, broken = TRUE) //Lets dump our contents by opening up return ..() @@ -536,9 +533,9 @@ layer = PROJECTILE_HIT_THRESHHOLD_LAYER /obj/effect/pod_landingzone_effect/Initialize(mapload, obj/structure/closet/supplypod/pod) + . = ..() transform = matrix() * 1.5 - animate(src, transform = matrix()*0.01, time = pod.landingDelay+pod.fallDuration) - ..() + animate(src, transform = matrix()*0.01, time = pod.delays[POD_TRANSIT]+pod.delays[POD_FALLING]) /obj/effect/pod_landingzone //This is the object that forceMoves the supplypod to it's location name = "Landing Zone Indicator" @@ -564,11 +561,12 @@ if (!pod.effectStealth) helper = new (drop_location(), pod) alpha = 255 - animate(src, transform = matrix().Turn(90), time = pod.landingDelay+pod.fallDuration) + animate(src, transform = matrix().Turn(90), time = pod.delays[POD_TRANSIT]+pod.delays[POD_FALLING]) if (single_order) if (istype(single_order, /datum/supply_order)) var/datum/supply_order/SO = single_order - SO.generate(pod) + if (SO.pack.crate_type) + SO.generate(pod) else if (istype(single_order, /atom/movable)) var/atom/movable/O = single_order O.forceMove(pod) @@ -576,16 +574,16 @@ mob_in_pod.reset_perspective(src) if(pod.effectStun) //If effectStun is true, stun any mobs caught on this pod_landingzone until the pod gets a chance to hit them for (var/mob/living/target_living in get_turf(src)) - target_living.Stun(pod.landingDelay+10, ignore_canstun = TRUE)//you ain't goin nowhere, kid. - if (pod.fallDuration == initial(pod.fallDuration) && pod.landingDelay + pod.fallDuration < pod.fallingSoundLength) + target_living.Stun(pod.delays[POD_TRANSIT]+10, ignore_canstun = TRUE)//you ain't goin nowhere, kid. + if (pod.delays[POD_FALLING] == initial(pod.delays[POD_FALLING]) && pod.delays[POD_TRANSIT] + pod.delays[POD_FALLING] < pod.fallingSoundLength) pod.fallingSoundLength = 3 //The default falling sound is a little long, so if the landing time is shorter than the default falling sound, use a special, shorter default falling sound pod.fallingSound = 'sound/weapons/mortar_whistle.ogg' - var/soundStartTime = pod.landingDelay - pod.fallingSoundLength + pod.fallDuration + var/soundStartTime = pod.delays[POD_TRANSIT] - pod.fallingSoundLength + pod.delays[POD_FALLING] if (soundStartTime < 0) soundStartTime = 1 if (!pod.effectQuiet && !(pod.pod_flags & FIRST_SOUNDS)) addtimer(CALLBACK(src, .proc/playFallingSound), soundStartTime) - addtimer(CALLBACK(src, .proc/beginLaunch, pod.effectCircle), pod.landingDelay) + addtimer(CALLBACK(src, .proc/beginLaunch, pod.effectCircle), pod.delays[POD_TRANSIT]) /obj/effect/pod_landingzone/proc/playFallingSound() playsound(src, pod.fallingSound, pod.soundVolume, TRUE, 6) @@ -606,9 +604,9 @@ pod.transform = matrix().Turn(rotation) pod.layer = FLY_LAYER if (pod.style != STYLE_INVISIBLE) - animate(pod.get_filter("motionblur"), y = 0, time = pod.fallDuration, flags = ANIMATION_PARALLEL) - animate(pod, pixel_z = -1 * abs(sin(rotation))*4, pixel_x = SUPPLYPOD_X_OFFSET + (sin(rotation) * 20), time = pod.fallDuration, easing = LINEAR_EASING, flags = ANIMATION_PARALLEL) //Make the pod fall! At an angle! - addtimer(CALLBACK(src, .proc/endLaunch), pod.fallDuration, TIMER_CLIENT_TIME) //Go onto the last step after a very short falling animation + animate(pod.get_filter("motionblur"), y = 0, time = pod.delays[POD_FALLING], flags = ANIMATION_PARALLEL) + animate(pod, pixel_z = -1 * abs(sin(rotation))*4, pixel_x = SUPPLYPOD_X_OFFSET + (sin(rotation) * 20), time = pod.delays[POD_FALLING], easing = LINEAR_EASING, flags = ANIMATION_PARALLEL) //Make the pod fall! At an angle! + addtimer(CALLBACK(src, .proc/endLaunch), pod.delays[POD_FALLING], TIMER_CLIENT_TIME) //Go onto the last step after a very short falling animation /obj/effect/pod_landingzone/proc/setupSmoke(rotation) if (pod.style == STYLE_INVISIBLE || pod.style == STYLE_SEETHROUGH) @@ -622,17 +620,17 @@ smoke_effects[i] = smoke_part smoke_part.pixel_x = sin(rotation)*32 * i smoke_part.pixel_y = abs(cos(rotation))*32 * i - smoke_part.filters += filter(type = "blur", size = 4) - var/time = (pod.fallDuration / length(smoke_effects))*(length(smoke_effects)-i) + smoke_part.add_filter("smoke_blur", 1, gauss_blur_filter(size = 4)) + var/time = (pod.delays[POD_FALLING] / length(smoke_effects))*(length(smoke_effects)-i) addtimer(CALLBACK(smoke_part, /obj/effect/supplypod_smoke/.proc/drawSelf, i), time, TIMER_CLIENT_TIME) //Go onto the last step after a very short falling animation - QDEL_IN(smoke_part, pod.fallDuration + 35) + QDEL_IN(smoke_part, pod.delays[POD_FALLING] + 35) /obj/effect/pod_landingzone/proc/drawSmoke() if (pod.style == STYLE_INVISIBLE || pod.style == STYLE_SEETHROUGH) return for (var/obj/effect/supplypod_smoke/smoke_part in smoke_effects) animate(smoke_part, alpha = 0, time = 20, flags = ANIMATION_PARALLEL) - animate(smoke_part.filters[1], size = 6, time = 15, easing = CUBIC_EASING|EASE_OUT, flags = ANIMATION_PARALLEL) + animate(smoke_part.get_filter("smoke_blur"), size = 6, time = 15, easing = CUBIC_EASING|EASE_OUT, flags = ANIMATION_PARALLEL) /obj/effect/pod_landingzone/proc/endLaunch() pod.tryMakeRubble(drop_location()) diff --git a/code/modules/chatter/chatter.dm b/code/modules/chatter/chatter.dm index 2dfd0bd77d..3da2b32a67 100644 --- a/code/modules/chatter/chatter.dm +++ b/code/modules/chatter/chatter.dm @@ -38,7 +38,7 @@ var/path = "sound/chatter/[phomeme]_[length].ogg" playsound(loc, path, - vol = 40, vary = 0, extrarange = 3, falloff = FALSE) + vol = 40, vary = 0, extrarange = 3) sleep((length + 1) * chatter_get_sleep_multiplier(phomeme)) diff --git a/code/modules/client/client_defines.dm b/code/modules/client/client_defines.dm index b71521121b..241cd6be93 100644 --- a/code/modules/client/client_defines.dm +++ b/code/modules/client/client_defines.dm @@ -31,6 +31,7 @@ var/datum/preferences/prefs = null var/last_turn = 0 var/move_delay = 0 + var/last_move = 0 var/area = null /// Last time we Click()ed. No clicking twice in one tick! diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm index c298d150f8..8add693d48 100644 --- a/code/modules/client/client_procs.dm +++ b/code/modules/client/client_procs.dm @@ -76,9 +76,15 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( to_chat(src, "Your previous action was ignored because you've done too many in a second") return - //Logs all hrefs, except chat pings - if(!(href_list["_src_"] == "chat" && href_list["proc"] == "ping" && LAZYLEN(href_list) == 2)) - log_href("[src] (usr:[usr]\[[COORD(usr)]\]) : [hsrc ? "[hsrc] " : ""][href]") + + // Tgui Topic middleware + if(tgui_Topic(href_list)) + if(CONFIG_GET(flag/emergency_tgui_logging)) + log_href("[src] (usr:[usr]\[[COORD(usr)]\]) : [hsrc ? "[hsrc] " : ""][href]") + return + + //Logs all hrefs + log_href("[src] (usr:[usr]\[[COORD(usr)]\]) : [hsrc ? "[hsrc] " : ""][href]") //byond bug ID:2256651 if (asset_cache_job && (asset_cache_job in completed_asset_jobs)) @@ -105,10 +111,6 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( handle_statpanel_click(href_list) return - // Tgui Topic middleware - if(tgui_Topic(href_list)) - return - // Admin PM if(href_list["priv_msg"]) cmd_admin_pm(href_list["priv_msg"],null) @@ -147,6 +149,8 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( /client/proc/handle_statpanel_click(list/href_list) var/atom/target = locate(href_list["statpanel_item_target"]) + if(!target) + return Click(target, target.loc, null, "[href_list["statpanel_item_shiftclick"]?"shift=1;":null][href_list["statpanel_item_ctrlclick"]?"ctrl=1;":null]&alt=[href_list["statpanel_item_altclick"]?"alt=1;":null]", FALSE, "statpanel") /client/proc/is_content_unlocked() @@ -415,7 +419,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( if (nnpa >= 0) message_admins("New user: [key_name_admin(src)] is connecting here for the first time.") if (CONFIG_GET(flag/irc_first_connection_alert)) - send2irc_adminless_only("New-user", "[key_name(src)] is connecting for the first time!") + send2tgs_adminless_only("New-user", "[key_name(src)] is connecting for the first time!") else if (isnum(cached_player_age) && cached_player_age < nnpa) message_admins("New user: [key_name_admin(src)] just connected with an age of [cached_player_age] day[(player_age==1?"":"s")]") if(CONFIG_GET(flag/use_account_age_for_jobs) && account_age >= 0) @@ -423,7 +427,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( if(account_age >= 0 && account_age < nnpa) message_admins("[key_name_admin(src)] (IP: [address], ID: [computer_id]) is a new BYOND account [account_age] day[(account_age==1?"":"s")] old, created on [account_join_date].") if (CONFIG_GET(flag/irc_first_connection_alert)) - send2irc_adminless_only("new_byond_user", "[key_name(src)] (IP: [address], ID: [computer_id]) is a new BYOND account [account_age] day[(account_age==1?"":"s")] old, created on [account_join_date].") + send2tgs_adminless_only("new_byond_user", "[key_name(src)] (IP: [address], ID: [computer_id]) is a new BYOND account [account_age] day[(account_age==1?"":"s")] old, created on [account_join_date].") get_message_output("watchlist entry", ckey) check_ip_intel() validate_key_in_db() @@ -496,7 +500,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( GLOB.directory -= ckey log_access("Logout: [key_name(src)]") GLOB.ahelp_tickets.ClientLogout(src) - // SSserver_maint.UpdateHubStatus() + SSserver_maint.UpdateHubStatus() if(credits) QDEL_LIST(credits) if(holder) @@ -519,7 +523,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( "Forever alone :("\ ) - send2irc("Server", "[cheesy_message] (No admins online)") + send2adminchat("Server", "[cheesy_message] (No admins online)") QDEL_LIST_ASSOC_VAL(char_render_holders) if(movingmob != null) movingmob.client_mobs_in_contents -= mob @@ -534,14 +538,21 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( return if(!SSdbcore.Connect()) return - var/sql_ckey = sanitizeSQL(src.ckey) - var/datum/DBQuery/query_get_related_ip = SSdbcore.NewQuery("SELECT ckey FROM [format_table_name("player")] WHERE ip = INET_ATON('[address]') AND ckey != '[sql_ckey]'") - query_get_related_ip.Execute() + var/datum/db_query/query_get_related_ip = SSdbcore.NewQuery( + "SELECT ckey FROM [format_table_name("player")] WHERE ip = INET_ATON(:address) AND ckey != :ckey", + list("address" = address, "ckey" = ckey) + ) + if(!query_get_related_ip.Execute()) + qdel(query_get_related_ip) + return related_accounts_ip = "" while(query_get_related_ip.NextRow()) related_accounts_ip += "[query_get_related_ip.item[1]], " qdel(query_get_related_ip) - var/datum/DBQuery/query_get_related_cid = SSdbcore.NewQuery("SELECT ckey FROM [format_table_name("player")] WHERE computerid = '[computer_id]' AND ckey != '[sql_ckey]'") + var/datum/db_query/query_get_related_cid = SSdbcore.NewQuery( + "SELECT ckey FROM [format_table_name("player")] WHERE computerid = :computerid AND ckey != :ckey", + list("computerid" = computer_id, "ckey" = ckey) + ) if(!query_get_related_cid.Execute()) qdel(query_get_related_cid) return @@ -555,45 +566,40 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( else if (!GLOB.deadmins[ckey] && check_randomizer(connectiontopic)) return - var/sql_ip = sanitizeSQL(address) - var/sql_computerid = sanitizeSQL(computer_id) - var/sql_admin_rank = sanitizeSQL(admin_rank) var/new_player - var/datum/DBQuery/query_client_in_db = SSdbcore.NewQuery("SELECT 1 FROM [format_table_name("player")] WHERE ckey = '[sql_ckey]'") + var/datum/db_query/query_client_in_db = SSdbcore.NewQuery( + "SELECT 1 FROM [format_table_name("player")] WHERE ckey = :ckey", + list("ckey" = ckey) + ) if(!query_client_in_db.Execute()) qdel(query_client_in_db) return - if(!query_client_in_db.NextRow()) //new user detected - if(!holder && !GLOB.deadmins[ckey]) - if(CONFIG_GET(flag/panic_bunker) && !(ckey in GLOB.bunker_passthrough)) - log_access("Failed Login: [key] - New account attempting to connect during panic bunker") - message_admins("Failed Login: [key] - New account attempting to connect during panic bunker") - to_chat(src, "You must first join the Discord to verify your account before joining this server.
    To do so, read the rules and post a request in the #station-access-requests channel under the \"Main server\" category in the Discord server linked here: https://discord.gg/E6SQuhz
    If you have already done so, wait a few minutes then try again; sometimes the server needs to fully load before you can join.
    ") //CIT CHANGE - makes the panic bunker disconnect message point to the discord - var/list/connectiontopic_a = params2list(connectiontopic) - var/list/panic_addr = CONFIG_GET(string/panic_server_address) - if(panic_addr && !connectiontopic_a["redirect"]) - var/panic_name = CONFIG_GET(string/panic_server_name) - to_chat(src, "Sending you to [panic_name ? panic_name : panic_addr].") - winset(src, null, "command=.options") - src << link("[panic_addr]?redirect=1") - qdel(query_client_in_db) - qdel(src) - return - new_player = 1 - account_join_date = sanitizeSQL(findJoinDate()) - var/sql_key = sanitizeSQL(key) - var/datum/DBQuery/query_add_player = SSdbcore.NewQuery("INSERT INTO [format_table_name("player")] (`ckey`, `byond_key`, `firstseen`, `firstseen_round_id`, `lastseen`, `lastseen_round_id`, `ip`, `computerid`, `lastadminrank`, `accountjoindate`) VALUES ('[sql_ckey]', '[sql_key]', Now(), '[GLOB.round_id]', Now(), '[GLOB.round_id]', INET_ATON('[sql_ip]'), '[sql_computerid]', '[sql_admin_rank]', [account_join_date ? "'[account_join_date]'" : "NULL"])") - if(!query_add_player.Execute()) - qdel(query_client_in_db) - qdel(query_add_player) - return - qdel(query_add_player) - if(!account_join_date) - account_join_date = "Error" - account_age = -1 - else if(ckey in GLOB.bunker_passthrough) - GLOB.bunker_passthrough -= ckey + //If we aren't an admin, and the flag is set + if(CONFIG_GET(flag/panic_bunker) && !holder && !GLOB.deadmins[ckey] && !(ckey in GLOB.bunker_passthrough)) + var/living_recs = CONFIG_GET(number/panic_bunker_living) + //Relies on pref existing, but this proc is only called after that occurs, so we're fine. + var/minutes = get_exp_living(pure_numeric = TRUE) + if(minutes <= living_recs) // && !CONFIG_GET(flag/panic_bunker_interview) + var/reject_message = "Failed Login: [key] - Account attempting to connect during panic bunker, but they do not have the required living time [minutes]/[living_recs]" + log_access(reject_message) + message_admins("[reject_message]") + var/message = CONFIG_GET(string/panic_bunker_message) + message = replacetext(message, "%minutes%", living_recs) + to_chat(src, message) + var/list/connectiontopic_a = params2list(connectiontopic) + var/list/panic_addr = CONFIG_GET(string/panic_server_address) + if(panic_addr && !connectiontopic_a["redirect"]) + var/panic_name = CONFIG_GET(string/panic_server_name) + to_chat(src, "Sending you to [panic_name ? panic_name : panic_addr].") + winset(src, null, "command=.options") + src << link("[panic_addr]?redirect=1") + qdel(query_client_in_db) + qdel(src) + return + + if(!query_client_in_db.NextRow()) + new_player = 1 if(CONFIG_GET(flag/age_verification)) //setup age verification if(!set_db_player_flags()) message_admins(usr, "ERROR: Unable to read player flags from database. Please check logs.") @@ -605,9 +611,24 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( update_flag_db(DB_FLAG_AGE_CONFIRMATION_COMPLETE, TRUE) else update_flag_db(DB_FLAG_AGE_CONFIRMATION_INCOMPLETE, TRUE) - + account_join_date = findJoinDate() + var/datum/db_query/query_add_player = SSdbcore.NewQuery({" + INSERT INTO [format_table_name("player")] (`ckey`, `byond_key`, `firstseen`, `firstseen_round_id`, `lastseen`, `lastseen_round_id`, `ip`, `computerid`, `lastadminrank`, `accountjoindate`) + VALUES (:ckey, :key, Now(), :round_id, Now(), :round_id, INET_ATON(:ip), :computerid, :adminrank, :account_join_date) + "}, list("ckey" = ckey, "key" = key, "round_id" = GLOB.round_id, "ip" = address, "computerid" = computer_id, "adminrank" = admin_rank, "account_join_date" = account_join_date || null)) + if(!query_add_player.Execute()) + qdel(query_client_in_db) + qdel(query_add_player) + return + qdel(query_add_player) + if(!account_join_date) + account_join_date = "Error" + account_age = -1 qdel(query_client_in_db) - var/datum/DBQuery/query_get_client_age = SSdbcore.NewQuery("SELECT firstseen, DATEDIFF(Now(),firstseen), accountjoindate, DATEDIFF(Now(),accountjoindate) FROM [format_table_name("player")] WHERE ckey = '[sql_ckey]'") + var/datum/db_query/query_get_client_age = SSdbcore.NewQuery( + "SELECT firstseen, DATEDIFF(Now(),firstseen), accountjoindate, DATEDIFF(Now(),accountjoindate) FROM [format_table_name("player")] WHERE ckey = :ckey", + list("ckey" = ckey) + ) if(!query_get_client_age.Execute()) qdel(query_get_client_age) return @@ -618,11 +639,14 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( account_join_date = query_get_client_age.item[3] account_age = text2num(query_get_client_age.item[4]) if(!account_age) - account_join_date = sanitizeSQL(findJoinDate()) + account_join_date = findJoinDate() if(!account_join_date) account_age = -1 else - var/datum/DBQuery/query_datediff = SSdbcore.NewQuery("SELECT DATEDIFF(Now(),'[account_join_date]')") + var/datum/db_query/query_datediff = SSdbcore.NewQuery( + "SELECT DATEDIFF(Now(), :account_join_date)", + list("account_join_date" = account_join_date) + ) if(!query_datediff.Execute()) qdel(query_datediff) return @@ -631,18 +655,24 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( qdel(query_datediff) qdel(query_get_client_age) if(!new_player) - var/datum/DBQuery/query_log_player = SSdbcore.NewQuery("UPDATE [format_table_name("player")] SET lastseen = Now(), lastseen_round_id = '[GLOB.round_id]', ip = INET_ATON('[sql_ip]'), computerid = '[sql_computerid]', lastadminrank = '[sql_admin_rank]', accountjoindate = [account_join_date ? "'[account_join_date]'" : "NULL"] WHERE ckey = '[sql_ckey]'") + var/datum/db_query/query_log_player = SSdbcore.NewQuery( + "UPDATE [format_table_name("player")] SET lastseen = Now(), lastseen_round_id = :round_id, ip = INET_ATON(:ip), computerid = :computerid, lastadminrank = :admin_rank, accountjoindate = :account_join_date WHERE ckey = :ckey", + list("round_id" = GLOB.round_id, "ip" = address, "computerid" = computer_id, "admin_rank" = admin_rank, "account_join_date" = account_join_date || null, "ckey" = ckey) + ) if(!query_log_player.Execute()) qdel(query_log_player) return qdel(query_log_player) if(!account_join_date) account_join_date = "Error" - var/datum/DBQuery/query_log_connection = SSdbcore.NewQuery("INSERT INTO `[format_table_name("connection_log")]` (`id`,`datetime`,`server_ip`,`server_port`,`round_id`,`ckey`,`ip`,`computerid`) VALUES(null,Now(),INET_ATON(IF('[world.internet_address]' LIKE '', '0', '[world.internet_address]')),'[world.port]','[GLOB.round_id]','[sql_ckey]',INET_ATON('[sql_ip]'),'[sql_computerid]')") + var/datum/db_query/query_log_connection = SSdbcore.NewQuery({" + INSERT INTO `[format_table_name("connection_log")]` (`id`,`datetime`,`server_ip`,`server_port`,`round_id`,`ckey`,`ip`,`computerid`) + VALUES(null,Now(),INET_ATON(:internet_address),:port,:round_id,:ckey,INET_ATON(:ip),:computerid) + "}, list("internet_address" = world.internet_address || "0", "port" = world.port, "round_id" = GLOB.round_id, "ckey" = ckey, "ip" = address, "computerid" = computer_id)) query_log_connection.Execute() qdel(query_log_connection) - // SSserver_maint.UpdateHubStatus() + SSserver_maint.UpdateHubStatus() if(new_player) player_age = -1 @@ -662,9 +692,11 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( CRASH("Age check regex failed for [src.ckey]") /client/proc/validate_key_in_db() - var/sql_ckey = sanitizeSQL(ckey) var/sql_key - var/datum/DBQuery/query_check_byond_key = SSdbcore.NewQuery("SELECT byond_key FROM [format_table_name("player")] WHERE ckey = '[sql_ckey]'") + var/datum/db_query/query_check_byond_key = SSdbcore.NewQuery( + "SELECT byond_key FROM [format_table_name("player")] WHERE ckey = :ckey", + list("ckey" = ckey) + ) if(!query_check_byond_key.Execute()) qdel(query_check_byond_key) return @@ -680,8 +712,11 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( if(F) var/regex/R = regex("\\tkey = \"(.+)\"") if(R.Find(F)) - var/web_key = sanitizeSQL(R.group[1]) - var/datum/DBQuery/query_update_byond_key = SSdbcore.NewQuery("UPDATE [format_table_name("player")] SET byond_key = '[web_key]' WHERE ckey = '[sql_ckey]'") + var/web_key = R.group[1] + var/datum/db_query/query_update_byond_key = SSdbcore.NewQuery( + "UPDATE [format_table_name("player")] SET byond_key = :byond_key WHERE ckey = :ckey", + list("byond_key" = web_key, "ckey" = ckey) + ) query_update_byond_key.Execute() qdel(query_update_byond_key) else @@ -698,8 +733,10 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( var/static/tokens = list() var/static/cidcheck_failedckeys = list() //to avoid spamming the admins if the same guy keeps trying. var/static/cidcheck_spoofckeys = list() - var/sql_ckey = sanitizeSQL(ckey) - var/datum/DBQuery/query_cidcheck = SSdbcore.NewQuery("SELECT computerid FROM [format_table_name("player")] WHERE ckey = '[sql_ckey]'") + var/datum/db_query/query_cidcheck = SSdbcore.NewQuery( + "SELECT computerid FROM [format_table_name("player")] WHERE ckey = :ckey", + list("ckey" = ckey) + ) query_cidcheck.Execute() var/lastcid @@ -718,7 +755,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( sleep(15 SECONDS) //Longer sleep here since this would trigger if a client tries to reconnect manually because the inital reconnect failed - //we sleep after telling the client to reconnect, so if we still exist something is up + //we sleep after telling the client to reconnect, so if we still exist something is up log_access("Forced disconnect: [key] [computer_id] [address] - CID randomizer check") qdel(src) @@ -732,7 +769,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( if (!cidcheck_failedckeys[ckey]) message_admins("[key_name(src)] has been detected as using a cid randomizer. Connection rejected.") - send2irc_adminless_only("CidRandomizer", "[key_name(src)] has been detected as using a cid randomizer. Connection rejected.") + send2tgs_adminless_only("CidRandomizer", "[key_name(src)] has been detected as using a cid randomizer. Connection rejected.") cidcheck_failedckeys[ckey] = TRUE note_randomizer_user() @@ -743,7 +780,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( else if (cidcheck_failedckeys[ckey]) message_admins("[key_name_admin(src)] has been allowed to connect after showing they removed their cid randomizer") - send2irc_adminless_only("CidRandomizer", "[key_name(src)] has been allowed to connect after showing they removed their cid randomizer.") + send2tgs_adminless_only("CidRandomizer", "[key_name(src)] has been allowed to connect after showing they removed their cid randomizer.") cidcheck_failedckeys -= ckey if (cidcheck_spoofckeys[ckey]) message_admins("[key_name_admin(src)] has been allowed to connect after appearing to have attempted to spoof a cid randomizer check because it appears they aren't spoofing one this time") @@ -774,10 +811,11 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( add_system_note("CID-Error", "Detected as using a cid randomizer.") /client/proc/add_system_note(system_ckey, message) - var/sql_system_ckey = sanitizeSQL(system_ckey) - var/sql_ckey = sanitizeSQL(ckey) //check to see if we noted them in the last day. - var/datum/DBQuery/query_get_notes = SSdbcore.NewQuery("SELECT id FROM [format_table_name("messages")] WHERE type = 'note' AND targetckey = '[sql_ckey]' AND adminckey = '[sql_system_ckey]' AND timestamp + INTERVAL 1 DAY < NOW() AND deleted = 0 AND expire_timestamp > NOW()") + var/datum/db_query/query_get_notes = SSdbcore.NewQuery( + "SELECT id FROM [format_table_name("messages")] WHERE type = 'note' AND targetckey = :targetckey AND adminckey = :adminckey AND timestamp + INTERVAL 1 DAY < NOW() AND deleted = 0 AND (expire_timestamp > NOW() OR expire_timestamp IS NULL)", + list("targetckey" = ckey, "adminckey" = system_ckey) + ) if(!query_get_notes.Execute()) qdel(query_get_notes) return @@ -786,7 +824,10 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( return qdel(query_get_notes) //regardless of above, make sure their last note is not from us, as no point in repeating the same note over and over. - query_get_notes = SSdbcore.NewQuery("SELECT adminckey FROM [format_table_name("messages")] WHERE targetckey = '[sql_ckey]' AND deleted = 0 AND expire_timestamp > NOW() ORDER BY timestamp DESC LIMIT 1") + query_get_notes = SSdbcore.NewQuery( + "SELECT adminckey FROM [format_table_name("messages")] WHERE targetckey = :targetckey AND deleted = 0 AND (expire_timestamp > NOW() OR expire_timestamp IS NULL) ORDER BY timestamp DESC LIMIT 1", + list("targetckey" = ckey) + ) if(!query_get_notes.Execute()) qdel(query_get_notes) return @@ -966,7 +1007,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( var/mob/living/M = mob M.update_damage_hud() if (prefs.auto_fit_viewport) - fit_viewport() + addtimer(CALLBACK(src,.verb/fit_viewport,10)) //Delayed to avoid wingets from Login calls. SEND_SIGNAL(mob, COMSIG_MOB_CLIENT_CHANGE_VIEW, src, old_view, actualview) /client/proc/generate_clickcatcher() @@ -1007,6 +1048,25 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( /client/proc/can_have_part(part_name) return prefs.pref_species.mutant_bodyparts[part_name] || (part_name in GLOB.unlocked_mutant_parts) +///Redirect proc that makes it easier to call the unlock achievement proc. Achievement type is the typepath to the award, user is the mob getting the award, and value is an optional variable used for leaderboard value increments +/client/proc/give_award(achievement_type, mob/user, value = 1) + return player_details.achievements.unlock(achievement_type, user, value) + +///Redirect proc that makes it easier to get the status of an achievement. Achievement type is the typepath to the award. +/client/proc/get_award_status(achievement_type, mob/user, value = 1) + return player_details.achievements.get_achievement_status(achievement_type) + +///Redirect proc that makes it easier to get the status of an achievement. Achievement type is the typepath to the award. +/client/proc/award_heart(heart_reason) + to_chat(src, "Someone awarded you a heart![heart_reason ? " They said: [heart_reason]!" : ""]") + if(!src) + return + prefs.hearted_until = world.realtime + (24 HOURS) + prefs.hearted = TRUE + if(!src) + return + prefs.save_preferences() + /// compiles a full list of verbs and sends it to the browser /client/proc/init_verbs() if(IsAdminAdvancedProcCall()) @@ -1046,3 +1106,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( return TRUE return FALSE +/client/proc/open_filter_editor(atom/in_atom) + if(holder) + holder.filteriffic = new /datum/filter_editor(in_atom) + holder.filteriffic.ui_interact(mob) diff --git a/code/modules/client/player_details.dm b/code/modules/client/player_details.dm index 6b2a936533..0c06d96b64 100644 --- a/code/modules/client/player_details.dm +++ b/code/modules/client/player_details.dm @@ -3,4 +3,21 @@ var/list/logging = list() var/list/post_login_callbacks = list() var/list/post_logout_callbacks = list() + var/list/played_names = list() //List of names this key played under this round var/byond_version = "Unknown" + var/datum/achievement_data/achievements + +/datum/player_details/New(key) + achievements = new(key) + +/proc/log_played_names(ckey, ...) + if(!ckey) + return + if(args.len < 2) + return + var/list/names = args.Copy(2) + var/datum/player_details/P = GLOB.player_details[ckey] + if(P) + for(var/name in names) + if(name) + P.played_names |= name diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm index 7c6ba30b80..16e1b45843 100644 --- a/code/modules/client/preferences.dm +++ b/code/modules/client/preferences.dm @@ -62,9 +62,15 @@ GLOBAL_LIST_EMPTY(preferences_datums) var/UI_style = null var/buttons_locked = FALSE var/hotkeys = FALSE + + ///Runechat preference. If true, certain messages will be displayed on the map, not ust on the chat area. Boolean. var/chat_on_map = TRUE + ///Limit preference on the size of the message. Requires chat_on_map to have effect. var/max_chat_length = CHAT_MESSAGE_MAX_LENGTH + ///Whether non-mob messages will be displayed, such as machine vendor announcements. Requires chat_on_map to have effect. Boolean. var/see_chat_non_mob = TRUE + ///Whether emotes will be displayed on runechat. Requires chat_on_map to have effect. Boolean. + var/see_rc_emotes = TRUE /// Custom Keybindings var/list/key_bindings = list() @@ -99,6 +105,8 @@ GLOBAL_LIST_EMPTY(preferences_datums) var/be_random_body = 0 //whether we'll have a random body every round var/gender = MALE //gender of character (well duh) var/age = 30 //age of character + var/language = "Random" //bonus language + var/choselanguage = "Random" //language appearance var/underwear = "Nude" //underwear type var/undie_color = "FFFFFF" var/undershirt = "Nude" //undershirt type @@ -160,13 +168,13 @@ GLOBAL_LIST_EMPTY(preferences_datums) var/auto_fit_viewport = FALSE ///Should we be in the widescreen mode set by the config? var/widescreenpref = TRUE - ///What size should pixels be displayed as? 0 is strech to fit var/pixel_size = 0 ///What scaling method should we use? var/scaling_method = "normal" - var/uplink_spawn_loc = UPLINK_PDA + ///The playtime_reward_cloak variable can be set to TRUE from the prefs menu only once the user has gained over 5K playtime hours. If true, it allows the user to get a cool looking roundstart cloak. + var/playtime_reward_cloak = FALSE var/hud_toggle_flash = TRUE var/hud_toggle_color = "#ffffff" @@ -209,8 +217,17 @@ GLOBAL_LIST_EMPTY(preferences_datums) var/autostand = TRUE var/auto_ooc = FALSE + ///This var stores the amount of points the owner will get for making it out alive. + var/hardcore_survival_score = 0 + + ///Someone thought we were nice! We get a little heart in OOC until we join the server past the below time (we can keep it until the end of the round otherwise) + var/hearted + ///If we have a hearted commendations, we honor it every time the player loads preferences until this time has been passed + var/hearted_until /// If we have persistent scars enabled var/persistent_scars = TRUE + ///If we want to broadcast deadchat connect/disconnect messages + var/broadcast_login_logout = TRUE /// We have 5 slots for persistent scars, if enabled we pick a random one to load (empty by default) and scars at the end of the shift if we survived as our original person var/list/scars_list = list("1" = "", "2" = "", "3" = "", "4" = "", "5" = "") /// Which of the 5 persistent scar slots we randomly roll to load for this round, if enabled. Actually rolled in [/datum/preferences/proc/load_character(slot)] @@ -218,6 +235,9 @@ GLOBAL_LIST_EMPTY(preferences_datums) var/hide_ckey = FALSE //pref for hiding if your ckey shows round-end or not + var/list/tcg_cards = list() + var/list/tcg_decks = list() + /datum/preferences/New(client/C) parent = C @@ -308,6 +328,7 @@ GLOBAL_LIST_EMPTY(preferences_datums) dat += "Gender: [gender == MALE ? "Male" : (gender == FEMALE ? "Female" : (gender == PLURAL ? "Non-binary" : "Object"))]
    " dat += "Age: [age]
    " + dat += "Language: [choselanguage]
    " dat += "Special Names:
    " var/old_group @@ -895,19 +916,23 @@ GLOBAL_LIST_EMPTY(preferences_datums) continue var/class_link = "" var/list/loadout_item = has_loadout_gear(loadout_slot, "[gear.type]") - var/extra_color_data = "" + var/extra_loadout_data = "" if(loadout_item) class_link = "style='white-space:normal;' class='linkOn' href='?_src_=prefs;preference=gear;toggle_gear_path=[html_encode(name)];toggle_gear=0'" if(gear.loadout_flags & LOADOUT_CAN_COLOR_POLYCHROMIC) - extra_color_data += "
    Color" + extra_loadout_data += "
    Color" for(var/loadout_color in loadout_item[LOADOUT_COLOR]) - extra_color_data += "   " + extra_loadout_data += "   " else var/loadout_color_non_poly = "#FFFFFF" if(length(loadout_item[LOADOUT_COLOR])) loadout_color_non_poly = loadout_item[LOADOUT_COLOR][1] - extra_color_data += "
    Color" - extra_color_data += "   " + extra_loadout_data += "
    Color" + extra_loadout_data += "   " + if(gear.loadout_flags & LOADOUT_CAN_NAME) + extra_loadout_data += "
    Name [loadout_item[LOADOUT_CUSTOM_NAME] ? loadout_item[LOADOUT_CUSTOM_NAME] : "N/A"]" + if(gear.loadout_flags & LOADOUT_CAN_DESCRIPTION) + extra_loadout_data += "
    Description" else if((gear_points - gear.cost) < 0) class_link = "style='white-space:normal;' class='linkOff'" else if(donoritem) @@ -916,7 +941,7 @@ GLOBAL_LIST_EMPTY(preferences_datums) class_link = "style='white-space:normal;' href='?_src_=prefs;preference=gear;toggle_gear_path=[html_encode(name)];toggle_gear=1'" else class_link = "style='white-space:normal;background:#eb2e2e;' class='linkOff'" - dat += "[name][extra_color_data]" + dat += "[name][extra_loadout_data]" dat += "[gear.cost]" if(islist(gear.restricted_roles)) if(gear.restricted_roles.len) @@ -929,14 +954,15 @@ GLOBAL_LIST_EMPTY(preferences_datums) dat += gear.restricted_roles.Join(";") dat += "
    " if(!istype(gear, /datum/gear/unlockable)) - dat += "[gear.description]" + // the below line essentially means "if the loadout item is picked by the user and has a custom description, give it the custom description, otherwise give it the default description" + dat += "[loadout_item ? (loadout_item[LOADOUT_CUSTOM_DESCRIPTION] ? loadout_item[LOADOUT_CUSTOM_DESCRIPTION] : gear.description) : gear.description]" else //we add the user's progress to the description assuming they have progress var/datum/gear/unlockable/unlockable = gear var/progress_made = unlockable_loadout_data[unlockable.progress_key] if(!progress_made) progress_made = 0 - dat += "[gear.description] Progress: [min(progress_made, unlockable.progress_required)]/[unlockable.progress_required]" + dat += "[loadout_item ? (loadout_item[LOADOUT_CUSTOM_DESCRIPTION] ? loadout_item[LOADOUT_CUSTOM_DESCRIPTION] : gear.description) : gear.description] Progress: [min(progress_made, unlockable.progress_required)]/[unlockable.progress_required]" dat += "" if(4) // Content preferences @@ -958,6 +984,7 @@ GLOBAL_LIST_EMPTY(preferences_datums) dat += "Breast Enlargement: [(cit_toggles & BREAST_ENLARGEMENT) ? "Allowed" : "Disallowed"]
    " dat += "Penis Enlargement: [(cit_toggles & PENIS_ENLARGEMENT) ? "Allowed" : "Disallowed"]
    " dat += "Hypno: [(cit_toggles & NEVER_HYPNO) ? "Disallowed" : "Allowed"]
    " + dat += "Aphrodisiacs: [(cit_toggles & NO_APHRO) ? "Disallowed" : "Allowed"]
    " dat += "Ass Slapping: [(cit_toggles & NO_ASS_SLAP) ? "Disallowed" : "Allowed"]
    " dat += "Automatic Wagging: [(cit_toggles & NO_AUTO_WAG) ? "Disabled" : "Enabled"]
    " dat += "" @@ -1343,9 +1370,11 @@ GLOBAL_LIST_EMPTY(preferences_datums) /datum/preferences/proc/process_link(mob/user, list/href_list) if(href_list["jobbancheck"]) - var/job = sanitizeSQL(href_list["jobbancheck"]) - var/sql_ckey = sanitizeSQL(user.ckey) - var/datum/DBQuery/query_get_jobban = SSdbcore.NewQuery("SELECT reason, bantime, duration, expiration_time, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("ban")].a_ckey), a_ckey) FROM [format_table_name("ban")] WHERE ckey = '[sql_ckey]' AND (bantype = 'JOB_PERMABAN' OR (bantype = 'JOB_TEMPBAN' AND expiration_time > Now())) AND isnull(unbanned) AND job = '[job]'") + var/job = href_list["jobbancheck"] + var/datum/db_query/query_get_jobban = SSdbcore.NewQuery({" + SELECT reason, bantime, duration, expiration_time, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("ban")].a_ckey), a_ckey) + FROM [format_table_name("ban")] WHERE ckey = :ckey AND (bantype = 'JOB_PERMABAN' OR (bantype = 'JOB_TEMPBAN' AND expiration_time > Now())) AND isnull(unbanned) AND job = :job + "}, list("ckey" = user.ckey, "job" = job)) if(!query_get_jobban.warn_execute()) qdel(query_get_jobban) return @@ -1360,7 +1389,7 @@ GLOBAL_LIST_EMPTY(preferences_datums) if(text2num(duration) > 0) text += ". The ban is for [duration] minutes and expires on [expiration_time] (server time)" text += "." - to_chat(user, text) + to_chat(user, text, confidential = TRUE) qdel(query_get_jobban) return @@ -1597,7 +1626,7 @@ GLOBAL_LIST_EMPTY(preferences_datums) for(var/modified_limb in modified_limbs) if(modified_limbs[modified_limb][1] == LOADOUT_LIMB_PROSTHETIC && modified_limb != limb_type) number_of_prosthetics += 1 - if(number_of_prosthetics > MAXIMUM_LOADOUT_PROSTHETICS) + if(number_of_prosthetics == MAXIMUM_LOADOUT_PROSTHETICS) to_chat(user, "You can only have up to two prosthetic limbs!") else //save the actual prosthetic data @@ -2310,6 +2339,28 @@ GLOBAL_LIST_EMPTY(preferences_datums) features["body_model"] = chosengender gender = chosengender + if("language") + choselanguage = input(user, "Select a language.", "Language", language) as null|anything in list("Beachtongue","Draconic","Dwarven", + "Chimpanzee","Space Sign Language","Random") + if(!choselanguage) + return + switch(choselanguage) + if("Rachidian") + language = /datum/language/arachnid + if("Beachtongue") + language = /datum/language/beachbum + if("Draconic") + language = /datum/language/draconic + if("Dwarven") + language = /datum/language/dwarf + if("Chimpanzee") + language = /datum/language/monkey + if("Space Sign Language") + language = /datum/language/signlanguage + if("Random") + language = pick(list("Rachidian", "Beachtongue","Draconic","Dwarven", + "Chimpanzee","Space Sign Language")) + if("body_size") var/new_body_size = input(user, "Choose your desired sprite size: (90-125%)\nWarning: This may make your character look distorted. Additionally, any size under 100% takes a 10% maximum health penalty", "Character Preference", features["body_size"]*100) as num|null if(new_body_size) @@ -2700,7 +2751,9 @@ GLOBAL_LIST_EMPTY(preferences_datums) loadout_data["SAVE_[loadout_slot]"] += list(new_loadout_data) //double packed because it does the union of the CONTENTS of the lists else loadout_data["SAVE_[loadout_slot]"] = list(new_loadout_data) //double packed because you somehow had no save slot in your loadout? - if(href_list["loadout_color"]) + + if(href_list["loadout_color"] || href_list["loadout_color_polychromic"] || href_list["loadout_rename"] || href_list["loadout_redescribe"]) + //if the gear doesn't exist, or they don't have it, ignore the request var/name = html_decode(href_list["loadout_gear_name"]) var/datum/gear/G = GLOB.loadout_items[gear_category][gear_subcategory][name] if(!G) @@ -2708,29 +2761,44 @@ GLOBAL_LIST_EMPTY(preferences_datums) var/user_gear = has_loadout_gear(loadout_slot, "[G.type]") if(!user_gear) return - if(!length(user_gear[LOADOUT_COLOR])) - user_gear[LOADOUT_COLOR] = list("#FFFFFF") - var/current_color = user_gear[LOADOUT_COLOR][1] - var/new_color = input(user, "Polychromic options", "Choose Color", current_color) as color|null - user_gear[LOADOUT_COLOR][1] = sanitize_hexcolor(new_color, 6, TRUE, current_color) - if(href_list["loadout_color_polychromic"]) - var/name = html_decode(href_list["loadout_gear_name"]) - var/datum/gear/G = GLOB.loadout_items[gear_category][gear_subcategory][name] - if(!G) - return - var/user_gear = has_loadout_gear(loadout_slot, "[G.type]") - if(!user_gear) - return - var/list/color_options = list() - for(var/i=1, i<=length(G.loadout_initial_colors), i++) - color_options += "Color [i]" - var/color_to_change = input(user, "Polychromic options", "Recolor [name]") as null|anything in color_options - if(color_to_change) - var/color_index = text2num(copytext(color_to_change, 7)) - var/current_color = user_gear[LOADOUT_COLOR][color_index] - var/new_color = input(user, "Polychromic options", "Choose [color_to_change] Color", current_color) as color|null - if(new_color) - user_gear[LOADOUT_COLOR][color_index] = sanitize_hexcolor(new_color, 6, TRUE, current_color) + + //possible requests: recolor, recolor (polychromic), rename, redescribe + //always make sure the gear allows said request before proceeding + + //non-poly coloring can only be done by non-poly items + if(href_list["loadout_color"] && !(G.loadout_flags & LOADOUT_CAN_COLOR_POLYCHROMIC)) + if(!length(user_gear[LOADOUT_COLOR])) + user_gear[LOADOUT_COLOR] = list("#FFFFFF") + var/current_color = user_gear[LOADOUT_COLOR][1] + var/new_color = input(user, "Polychromic options", "Choose Color", current_color) as color|null + user_gear[LOADOUT_COLOR][1] = sanitize_hexcolor(new_color, 6, TRUE, current_color) + + //poly coloring can only be done by poly items + if(href_list["loadout_color_polychromic"] && (G.loadout_flags & LOADOUT_CAN_COLOR_POLYCHROMIC)) + var/list/color_options = list() + for(var/i=1, i<=length(G.loadout_initial_colors), i++) + color_options += "Color [i]" + var/color_to_change = input(user, "Polychromic options", "Recolor [name]") as null|anything in color_options + if(color_to_change) + var/color_index = text2num(copytext(color_to_change, 7)) + var/current_color = user_gear[LOADOUT_COLOR][color_index] + var/new_color = input(user, "Polychromic options", "Choose [color_to_change] Color", current_color) as color|null + if(new_color) + user_gear[LOADOUT_COLOR][color_index] = sanitize_hexcolor(new_color, 6, TRUE, current_color) + + //both renaming and redescribing strip the input to stop html injection + + //renaming is only allowed if it has the flag for it + if(href_list["loadout_rename"] && (G.loadout_flags & LOADOUT_CAN_NAME)) + var/new_name = stripped_input(user, "Enter new name for item. Maximum [MAX_NAME_LEN] characters.", "Loadout Item Naming", null, MAX_NAME_LEN) + if(new_name) + user_gear[LOADOUT_CUSTOM_NAME] = new_name + + //redescribing is only allowed if it has the flag for it + if(href_list["loadout_redescribe"] && (G.loadout_flags & LOADOUT_CAN_DESCRIPTION)) //redescribe isnt a real word but i can't think of the right term to use + var/new_description = stripped_input(user, "Enter new description for item. Maximum 500 characters.", "Loadout Item Redescribing", null, 500) + if(new_description) + user_gear[LOADOUT_CUSTOM_DESCRIPTION] = new_description ShowChoices(user) return 1 diff --git a/code/modules/client/preferences_savefile.dm b/code/modules/client/preferences_savefile.dm index 0db9fbb66c..141346acde 100644 --- a/code/modules/client/preferences_savefile.dm +++ b/code/modules/client/preferences_savefile.dm @@ -271,7 +271,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car loadout_data["SAVE_[i]"] = list() for(var/some_gear_item in saved_loadout_paths) if(!ispath(text2path(some_gear_item))) - message_admins("Failed to copy item [some_gear_item] to new loadout system when migrating from version [current_version] to 40, issue: item is not a path") + log_game("Failed to copy item [some_gear_item] to new loadout system when migrating from version [current_version] to 40, issue: item is not a path") continue var/datum/gear/gear_item = text2path(some_gear_item) if(!(initial(gear_item.loadout_flags) & LOADOUT_CAN_COLOR_POLYCHROMIC)) @@ -604,6 +604,8 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car S["body_model"] >> features["body_model"] S["body_size"] >> features["body_size"] S["age"] >> age + S["language"] >> language + S["choselanguage"] >> choselanguage S["hair_color"] >> hair_color S["facial_hair_color"] >> facial_hair_color S["eye_type"] >> eye_type @@ -656,6 +658,21 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car modified_limbs = safe_json_decode(limbmodstr) else modified_limbs = list() + + var/tcgcardstr + S["tcg_cards"] >> tcgcardstr + if(length(tcgcardstr)) + tcg_cards = safe_json_decode(tcgcardstr) + else + tcg_cards = list() + + var/tcgdeckstr + S["tcg_decks"] >> tcgdeckstr + if(length(tcgdeckstr)) + tcg_decks = safe_json_decode(tcgdeckstr) + else + tcg_decks = list() + S["chosen_limb_id"] >> chosen_limb_id S["hide_ckey"] >> hide_ckey //saved per-character @@ -947,6 +964,8 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car WRITE_FILE(S["body_model"] , features["body_model"]) WRITE_FILE(S["body_size"] , features["body_size"]) WRITE_FILE(S["age"] , age) + WRITE_FILE(S["language"] , language) + WRITE_FILE(S["choselanguage"] , choselanguage) WRITE_FILE(S["hair_color"] , hair_color) WRITE_FILE(S["facial_hair_color"] , facial_hair_color) WRITE_FILE(S["eye_type"] , eye_type) @@ -1091,6 +1110,16 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car else S["loadout"] << safe_json_encode(list()) + if(length(tcg_cards)) + S["tcg_cards"] << safe_json_encode(tcg_cards) + else + S["tcg_cards"] << safe_json_encode(list()) + + if(length(tcg_decks)) + S["tcg_decks"] << safe_json_encode(tcg_decks) + else + S["tcg_decks"] << safe_json_encode(list()) + cit_character_pref_save(S) return 1 diff --git a/code/modules/client/verbs/aooc.dm b/code/modules/client/verbs/aooc.dm index 1a019bba80..182975d192 100644 --- a/code/modules/client/verbs/aooc.dm +++ b/code/modules/client/verbs/aooc.dm @@ -13,7 +13,7 @@ GLOBAL_VAR_INIT(normal_aooc_colour, "#ce254f") if(!mob) return - if(!(prefs.toggles & CHAT_OOC)) + if(!(prefs.chat_toggles & CHAT_OOC)) to_chat(src, " You have OOC muted.") return if(jobban_isbanned(mob, "OOC")) diff --git a/code/modules/client/verbs/ooc.dm b/code/modules/client/verbs/ooc.dm index c242509344..07087e70a3 100644 --- a/code/modules/client/verbs/ooc.dm +++ b/code/modules/client/verbs/ooc.dm @@ -179,7 +179,7 @@ GLOBAL_VAR_INIT(normal_ooc_colour, "#002eb8") to_chat(usr, "Sorry, that function is not enabled on this server.") return - browse_messages(null, usr.ckey, null, TRUE, override = TRUE) + browse_messages(null, usr.ckey, null, TRUE) /client/proc/self_playtime() set name = "View tracked playtime" @@ -190,11 +190,7 @@ GLOBAL_VAR_INIT(normal_ooc_colour, "#002eb8") to_chat(usr, "Sorry, tracking is currently disabled.") return - var/list/body = list() - body += "Playtime for [key]
    Playtime:" - body += get_exp_report() - body += "" - usr << browse(body.Join(), "window=playerplaytime[ckey];size=550x615") + new /datum/job_report_menu(src, usr) /client/proc/ignore_key(client) var/client/C = client @@ -233,7 +229,14 @@ GLOBAL_VAR_INIT(normal_ooc_colour, "#002eb8") set category = "OOC" set desc = "View the last round end report you've seen" - SSticker.show_roundend_report(src, TRUE) + SSticker.show_roundend_report(src, report_type = PERSONAL_LAST_ROUND) + +/client/proc/show_servers_last_roundend_report() + set name = "Server's Last Round" + set category = "OOC" + set desc = "View the last round end report from this server" + + SSticker.show_roundend_report(src, report_type = SERVER_LAST_ROUND) /client/verb/fit_viewport() set name = "Fit Viewport" @@ -245,8 +248,20 @@ GLOBAL_VAR_INIT(normal_ooc_colour, "#002eb8") var/aspect_ratio = view_size[1] / view_size[2] // Calculate desired pixel width using window size and aspect ratio - var/sizes = params2list(winget(src, "mainwindow.split;mapwindow", "size")) - var/map_size = splittext(sizes["mapwindow.size"], "x") + var/list/sizes = params2list(winget(src, "mainwindow.split;mapwindow", "size")) + + // Client closed the window? Some other error? This is unexpected behaviour, let's + // CRASH with some info. + if(!sizes["mapwindow.size"]) + CRASH("sizes does not contain mapwindow.size key. This means a winget failed to return what we wanted. --- sizes var: [sizes] --- sizes length: [length(sizes)]") + + var/list/map_size = splittext(sizes["mapwindow.size"], "x") + + // Looks like we expect mapwindow.size to be "ixj" where i and j are numbers. + // If we don't get our expected 2 outputs, let's give some useful error info. + if(length(map_size) != 2) + CRASH("map_size of incorrect length --- map_size var: [map_size] --- map_size length: [length(map_size)]") + var/height = text2num(map_size[2]) var/desired_width = round(height * aspect_ratio) if (text2num(map_size[1]) == desired_width) @@ -256,6 +271,9 @@ GLOBAL_VAR_INIT(normal_ooc_colour, "#002eb8") var/split_size = splittext(sizes["mainwindow.split.size"], "x") var/split_width = text2num(split_size[1]) + // Avoid auto-resizing the statpanel and chat into nothing. + desired_width = min(desired_width, split_width - 300) + // Calculate and apply a best estimate // +4 pixels are for the width of the splitter's handle var/pct = 100 * (desired_width + 4) / split_width diff --git a/code/modules/clothing/clothing.dm b/code/modules/clothing/clothing.dm index ea01b0c0ed..018c3d9a3f 100644 --- a/code/modules/clothing/clothing.dm +++ b/code/modules/clothing/clothing.dm @@ -31,6 +31,9 @@ // What items can be consumed to repair this clothing (must by an /obj/item/stack) var/repairable_by = /obj/item/stack/sheet/cloth + // has this item been upgraded by an upgrade kit (see: durathread armor kits) + var/upgrade_prefix + //Var modification - PLEASE be careful with this I know who you are and where you live var/list/user_vars_to_edit //VARNAME = VARVALUE eg: "name" = "butts" var/list/user_vars_remembered //Auto built by the above + dropped() + equipped() @@ -120,6 +123,8 @@ update_clothes_damaged_state(CLOTHING_PRISTINE) obj_integrity = max_integrity name = initial(name) // remove "tattered" or "shredded" if there's a prefix + if(upgrade_prefix) + name = upgrade_prefix + " " + initial(name) body_parts_covered = initial(body_parts_covered) slot_flags = initial(slot_flags) damage_by_parts = null diff --git a/code/modules/clothing/glasses/_glasses.dm b/code/modules/clothing/glasses/_glasses.dm index 11de0124cb..c79dee926c 100644 --- a/code/modules/clothing/glasses/_glasses.dm +++ b/code/modules/clothing/glasses/_glasses.dm @@ -311,7 +311,7 @@ to_chat(user, "Install a new flash in [src]!") /obj/item/clothing/glasses/sunglasses/stunglasses/attackby(obj/item/W,mob/user) - if (istype(W,/obj/item/screwdriver)) + if (W.tool_behaviour == TOOL_SCREWDRIVER) if (installed) installed.forceMove(get_turf(src)) to_chat(user, "You remove [installed] from [src].") diff --git a/code/modules/clothing/gloves/color.dm b/code/modules/clothing/gloves/color.dm index 56d6e7d38f..836b845faa 100644 --- a/code/modules/clothing/gloves/color.dm +++ b/code/modules/clothing/gloves/color.dm @@ -99,7 +99,7 @@ name = "fingerless insulated gloves" /obj/item/clothing/gloves/color/yellow/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/wirecutters)) + if(I.tool_behaviour == TOOL_WIRECUTTER) if(can_be_cut && icon_state == initial(icon_state))//only if not dyed to_chat(user, "You snip the fingertips off of [src].") I.play_tool_sound(src) @@ -108,7 +108,7 @@ ..() /obj/item/clothing/gloves/color/fyellow/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/wirecutters)) + if(I.tool_behaviour == TOOL_WIRECUTTER) if(can_be_cut && icon_state == initial(icon_state))//only if not dyed to_chat(user, "You snip the fingertips off of [src].") I.play_tool_sound(src) @@ -130,7 +130,7 @@ strip_mod = 1.2 /obj/item/clothing/gloves/color/black/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/wirecutters)) + if(I.tool_behaviour == TOOL_WIRECUTTER) if(can_be_cut && icon_state == initial(icon_state))//only if not dyed to_chat(user, "You snip the fingertips off of [src].") I.play_tool_sound(src) diff --git a/code/modules/clothing/gloves/miscellaneous.dm b/code/modules/clothing/gloves/miscellaneous.dm index af55c6b9d7..a558abbfe8 100644 --- a/code/modules/clothing/gloves/miscellaneous.dm +++ b/code/modules/clothing/gloves/miscellaneous.dm @@ -36,7 +36,7 @@ /obj/item/clothing/gloves/fingerless/pugilist/equipped(mob/user, slot) . = ..() - if(current_equipped_slot == SLOT_GLOVES) + if(slot == SLOT_GLOVES) use_buffs(user, TRUE) wornonce = TRUE @@ -161,6 +161,68 @@ return NO_AUTO_CLICKDELAY_HANDLING | ATTACK_IGNORE_ACTION +/obj/item/clothing/gloves/fingerless/ablative + name = "ablative armwraps" + desc = "Armwraps made out of a highly durable, reflective metal. Has the side effect of absorbing shocks." + siemens_coefficient = 0 + icon_state = "ablative_armwraps" + item_state = "ablative_armwraps" + block_parry_data = /datum/block_parry_data/ablative_armwraps + var/wornonce = FALSE + +/obj/item/clothing/gloves/fingerless/ablative/proc/get_component_parry_data(datum/source, parrying_method, datum/parrying_item_mob_or_art, list/backup_items, list/override) + if(parrying_method && !(parrying_method == UNARMED_PARRY)) + return + override[src] = ITEM_PARRY + +/obj/item/clothing/gloves/fingerless/ablative/equipped(mob/user, slot) + . = ..() + if(current_equipped_slot == SLOT_GLOVES) + RegisterSignal(user, COMSIG_LIVING_ACTIVE_PARRY_START, .proc/get_component_parry_data) + wornonce = TRUE + +/obj/item/clothing/gloves/fingerless/ablative/dropped(mob/user) + . = ..() + if(wornonce) + UnregisterSignal(user, COMSIG_LIVING_ACTIVE_PARRY_START) + wornonce = FALSE + +/obj/item/clothing/gloves/fingerless/ablative/can_active_parry(mob/user) + var/mob/living/carbon/human/H = user + if(!istype(H)) + return FALSE + return src == H.gloves + +/obj/item/clothing/gloves/fingerless/ablative/on_active_parry(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, list/block_return, parry_efficiency, parry_time) + . = ..() + if(parry_efficiency > 0) + owner.visible_message("[owner] deflects \the [object] with their armwraps!") + +/datum/block_parry_data/ablative_armwraps + parry_stamina_cost = 4 + parry_attack_types = ATTACK_TYPE_UNARMED | ATTACK_TYPE_PROJECTILE | ATTACK_TYPE_TACKLE | ATTACK_TYPE_THROWN | ATTACK_TYPE_MELEE + parry_flags = NONE + + parry_time_windup = 0 + parry_time_spindown = 0 + parry_time_active = 7.5 + + parry_time_perfect = 1 + parry_time_perfect_leeway = 7.5 + parry_imperfect_falloff_percent = 20 + parry_efficiency_perfect = 100 + parry_time_perfect_leeway_override = list( + TEXT_ATTACK_TYPE_MELEE = 1 + ) + + parry_efficiency_considered_successful = 0.01 + parry_efficiency_to_counterattack = INFINITY // no auto counter + parry_max_attacks = INFINITY + parry_failed_cooldown_duration = 2.25 SECONDS + parry_failed_stagger_duration = 2.25 SECONDS + parry_cooldown = 0 + parry_failed_clickcd_duration = 0 + /obj/item/clothing/gloves/botanic_leather name = "botanist's leather gloves" desc = "These leather gloves protect against thorns, barbs, prickles, spikes and other harmful objects of floral origin. They're also quite warm." diff --git a/code/modules/clothing/head/_head.dm b/code/modules/clothing/head/_head.dm index e646d3b202..6ad3a19694 100644 --- a/code/modules/clothing/head/_head.dm +++ b/code/modules/clothing/head/_head.dm @@ -40,11 +40,11 @@ if(iscyborg(hit_atom)) var/mob/living/silicon/robot/R = hit_atom ///hats in the borg's blacklist bounce off - if(!is_type_in_typecache(src, R.equippable_hats) || R.hat_offset == INFINITY) - R.visible_message("[src] bounces off [R]!", "[src] bounces off you, falling to the floor.") + if(is_type_in_typecache(src, GLOB.blacklisted_borg_hats)) + R.visible_message("[src] bounces off [R]!", "[src] bounces off you, falling to the floor.") return else - R.visible_message("[src] lands neatly on top of [R].", "[src] lands perfectly on top of you.") + R.visible_message("[src] lands neatly on top of [R]!", "[src] lands perfectly on top of you.") R.place_on_head(src) //hats aren't designed to snugly fit borg heads or w/e so they'll always manage to knock eachother off diff --git a/code/modules/clothing/head/helmet.dm b/code/modules/clothing/head/helmet.dm index 662318fb82..de68883a7b 100644 --- a/code/modules/clothing/head/helmet.dm +++ b/code/modules/clothing/head/helmet.dm @@ -313,7 +313,7 @@ A.Grant(user) return - if(istype(I, /obj/item/screwdriver)) + if(I.tool_behaviour == TOOL_SCREWDRIVER) if(F) for(var/obj/item/flashlight/seclite/S in src) to_chat(user, "You unscrew the seclite from [src].") diff --git a/code/modules/clothing/masks/gasmask.dm b/code/modules/clothing/masks/gasmask.dm index 15a5345ad1..525db577e0 100644 --- a/code/modules/clothing/masks/gasmask.dm +++ b/code/modules/clothing/masks/gasmask.dm @@ -17,7 +17,7 @@ /obj/item/clothing/mask/gas/examine(mob/user) . = ..() if(flavor_adjust) - . += "Alt-click to toggle identity concealment. it's currently [flags_inv & HIDEFACE ? "on" : "off"]." + . += "Alt-click to toggle identity concealment. It's currently [flags_inv & HIDEFACE ? "on" : "off"]." /obj/item/clothing/mask/gas/AltClick(mob/user) . = ..() @@ -143,14 +143,17 @@ "Blanc" = image(icon = src.icon, icon_state = "mime"), "Excité" = image(icon = src.icon, icon_state = "sexymime"), "Triste" = image(icon = src.icon, icon_state = "sadmime"), - "Effrayé" = image(icon = src.icon, icon_state = "scaredmime") + "Effrayé" = image(icon = src.icon, icon_state = "scaredmime"), + "Timid Woman" = image(icon = src.icon, icon_state = "timidwoman"), + "Timid Man" = image(icon = src.icon, icon_state = "timidman") ) /obj/item/clothing/mask/gas/mime/ui_action_click(mob/user) if(!istype(user) || user.incapacitated()) return - var/static/list/options = list("Blanc" = "mime", "Triste" = "sadmime", "Effrayé" = "scaredmime", "Excité" ="sexymime") + var/static/list/options = list("Blanc" = "mime", "Triste" = "sadmime", "Effrayé" = "scaredmime", "Excité" ="sexymime", + "Timid Woman" = "timidwoman", "Timid Man" = "timidman") var/choice = show_radial_menu(user,src, mimemask_designs, custom_check = FALSE, radius = 36, require_near = TRUE) @@ -170,6 +173,20 @@ item_state = "sexymime" actions_types = list() +/obj/item/clothing/mask/gas/timidcostume + name = "timid woman mask" + desc = "Most people who wear these are not really that timid." + clothing_flags = ALLOWINTERNALS + icon_state = "timidwoman" + item_state = "timidwoman" + flags_cover = MASKCOVERSEYES + resistance_flags = FLAMMABLE + +/obj/item/clothing/mask/gas/timidcostume/man + name = "timid man mask" + icon_state = "timidman" + item_state = "timidman" + /obj/item/clothing/mask/gas/monkeymask name = "monkey mask" desc = "A mask used when acting as a monkey." diff --git a/code/modules/clothing/masks/miscellaneous.dm b/code/modules/clothing/masks/miscellaneous.dm index 5dd35788f1..fe08cbd63e 100644 --- a/code/modules/clothing/masks/miscellaneous.dm +++ b/code/modules/clothing/masks/miscellaneous.dm @@ -33,6 +33,16 @@ actions_types = list(/datum/action/item_action/adjust) mutantrace_variation = STYLE_MUZZLE +/obj/item/clothing/mask/surgical/aesthetic + name = "aesthetic sterile mask" + desc = "A sterile mask designed to help prevent the spread of diseases. This one doesn't seem like it does a whole lot, somehow." + flags_inv = null + flags_cover = null + visor_flags_inv = null + visor_flags_cover = null + permeability_coefficient = 1 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + /obj/item/clothing/mask/surgical/attack_self(mob/user) adjustmask(user) diff --git a/code/modules/clothing/neck/_neck.dm b/code/modules/clothing/neck/_neck.dm index 51a526d089..b0af7aa0d5 100644 --- a/code/modules/clothing/neck/_neck.dm +++ b/code/modules/clothing/neck/_neck.dm @@ -237,6 +237,12 @@ icon_state = "choker" poly_colors = list("#222222") +/obj/item/clothing/neck/necklace/cowbell + name = "cowbell collar" + desc = "Who would wear this? Take this off, you aren't a cow, you're just an awful degenerate." + icon = 'icons/obj/clothing/neck.dmi' + icon_state = "cowbell" + /obj/item/key/collar name = "Collar Key" desc = "A key for a tiny lock on a collar or bag." @@ -289,7 +295,7 @@ //VERY SUPER BADASS NECKERCHIEFS// ////////////////////////////////// -obj/item/clothing/neck/neckerchief +/obj/item/clothing/neck/neckerchief icon = 'icons/obj/clothing/masks.dmi' //In order to reuse the bandana sprite w_class = WEIGHT_CLASS_TINY var/sourceBandanaType diff --git a/code/modules/clothing/shoes/colour.dm b/code/modules/clothing/shoes/colour.dm index df0f03f614..9d51753c78 100644 --- a/code/modules/clothing/shoes/colour.dm +++ b/code/modules/clothing/shoes/colour.dm @@ -51,6 +51,17 @@ desc = "Very gay shoes." icon_state = "rain_bow" +/obj/item/clothing/shoes/sneakers/poly/polychromic + name = "polychromic shoes" + desc = "Every color." + icon_state = "poly" + item_state = "poly" + var/list/poly_colors = list("#FFFFFF", "#1D1D1D") + +/obj/item/clothing/shoes/sneakers/poly/polychromic/ComponentInitialize() + . = ..() + AddElement(/datum/element/polychromic, poly_colors, 2) + /obj/item/clothing/shoes/sneakers/orange name = "orange shoes" icon_state = "orange" diff --git a/code/modules/clothing/shoes/miscellaneous.dm b/code/modules/clothing/shoes/miscellaneous.dm index b0d760ebd9..cc14424980 100644 --- a/code/modules/clothing/shoes/miscellaneous.dm +++ b/code/modules/clothing/shoes/miscellaneous.dm @@ -97,12 +97,14 @@ SEND_SIGNAL(t_loc, COMSIG_TURF_MAKE_DRY, TURF_WET_WATER, TRUE, INFINITY) /obj/item/clothing/shoes/clown_shoes - desc = "The prankster's standard-issue clowning shoes. Damn, they're huge!" + desc = "The prankster's standard-issue clowning shoes. Damn, they're huge! Ctrl-click to toggle waddle dampeners." name = "clown shoes" icon_state = "clown_shoes" slowdown = SHOES_SLOWDOWN+1 pocket_storage_component_path = /datum/component/storage/concrete/pockets/shoes/clown lace_time = 20 SECONDS // how the hell do these laces even work?? + var/datum/component/waddle + var/enabled_waddle = TRUE /obj/item/clothing/shoes/clown_shoes/Initialize() . = ..() @@ -110,14 +112,31 @@ /obj/item/clothing/shoes/clown_shoes/equipped(mob/user, slot) . = ..() - if(user.mind && HAS_TRAIT(user.mind, TRAIT_CLOWN_MENTALITY)) - SEND_SIGNAL(user, COMSIG_CLEAR_MOOD_EVENT, "noshoes") + if(slot == SLOT_SHOES) + if(enabled_waddle) + waddle = user.AddComponent(/datum/component/waddling) + if(user.mind && HAS_TRAIT(user.mind, TRAIT_CLOWN_MENTALITY)) + SEND_SIGNAL(user, COMSIG_CLEAR_MOOD_EVENT, "noshoes") /obj/item/clothing/shoes/clown_shoes/dropped(mob/user) . = ..() + QDEL_NULL(waddle) if(user.mind && HAS_TRAIT(user.mind, TRAIT_CLOWN_MENTALITY)) SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "noshoes", /datum/mood_event/noshoes) +/obj/item/clothing/shoes/clown_shoes/CtrlClick(mob/living/user) + if(!isliving(user)) + return + if(user.get_active_held_item() != src) + to_chat(user, "You must hold the [src] in your hand to do this!") + return + if (!enabled_waddle) + to_chat(user, "You switch off the waddle dampeners!") + enabled_waddle = TRUE + else + to_chat(user, "You switch on the waddle dampeners!") + enabled_waddle = FALSE + /obj/item/clothing/shoes/clown_shoes/jester name = "jester shoes" desc = "A court jester's shoes, updated with modern squeaking technology." @@ -413,6 +432,22 @@ var/wallcharges = 4 var/newlocobject = null +/obj/item/clothing/shoes/timidcostume + name = "timid woman boots" + desc = "Ready to rock your hips back and forth? These boots have a polychromic finish." + icon_state = "timidwoman" + item_state = "timidwoman" + +/obj/item/clothing/shoes/timidcostume/ComponentInitialize() + . = ..() + AddElement(/datum/element/polychromic, list("#0094FF"), 1) + +/obj/item/clothing/shoes/timidcostume/man + name = "timid man shoes" + desc = "Ready to go kart racing? These shoes have a polychromic finish." + icon_state = "timidman" + item_state = "timidman" + /obj/item/clothing/shoes/wallwalkers/equipped(mob/user,slot) . = ..() if(slot == SLOT_SHOES) diff --git a/code/modules/clothing/spacesuits/hardsuit.dm b/code/modules/clothing/spacesuits/hardsuit.dm index 40ad6dddd1..4af9e7387d 100644 --- a/code/modules/clothing/spacesuits/hardsuit.dm +++ b/code/modules/clothing/spacesuits/hardsuit.dm @@ -122,7 +122,7 @@ jetpack = I to_chat(user, "You successfully install the jetpack into [src].") return - else if(istype(I, /obj/item/screwdriver)) + else if(I.tool_behaviour == TOOL_SCREWDRIVER) if(!jetpack) to_chat(user, "[src] has no jetpack installed.") return diff --git a/code/modules/clothing/suits/cloaks.dm b/code/modules/clothing/suits/cloaks.dm index 133956e44e..b56f689979 100644 --- a/code/modules/clothing/suits/cloaks.dm +++ b/code/modules/clothing/suits/cloaks.dm @@ -102,7 +102,18 @@ /obj/item/clothing/neck/cloak/polychromic/ComponentInitialize() . = ..() AddElement(/datum/element/polychromic, poly_colors, 3) - + +/obj/item/clothing/neck/cancloak/polychromic + name = "canvas cloak" + desc = "A rugged cloak made of canvas." + icon_state = "cancloak" + item_state = "cloak" + var/list/poly_colors = list("#585858", "#373737", "#BEBEBE") + +/obj/item/clothing/neck/cancloak/polychromic/ComponentInitialize() + . = ..() + AddElement(/datum/element/polychromic, poly_colors, 3) + /obj/item/clothing/neck/cloak/alt name = "cloak" desc = "A ragged up white cloak. It reminds you of a place not far from here." diff --git a/code/modules/clothing/suits/labcoat.dm b/code/modules/clothing/suits/labcoat.dm index c7d5a4ea56..a27d82a544 100644 --- a/code/modules/clothing/suits/labcoat.dm +++ b/code/modules/clothing/suits/labcoat.dm @@ -55,7 +55,7 @@ /obj/item/clothing/suit/toggle/labcoat/virologist name = "virologist labcoat" - desc = "A suit that protects against minor chemical spills. Offers slightly more protection against biohazards than the standard model. Has a green stripe on the shoulder." + desc = "A suit that protects against minor chemical spills. Has a green stripe on the shoulder." icon_state = "labcoat_vir" /obj/item/clothing/suit/toggle/labcoat/science @@ -63,6 +63,11 @@ desc = "A suit that protects against minor chemical spills. Has a purple stripe on the shoulder." icon_state = "labcoat_tox" +/obj/item/clothing/suit/toggle/labcoat/roboticist + name = "roboticist labcoat" + desc = "More like an eccentric coat than a labcoat. Helps pass off bloodstains as part of the aesthetic. Comes with red shoulder pads." + icon_state = "labcoat_robo" + // Departmental Jackets /obj/item/clothing/suit/toggle/labcoat/depjacket mutantrace_variation = STYLE_DIGITIGRADE|STYLE_NO_ANTHRO_ICON diff --git a/code/modules/clothing/suits/miscellaneous.dm b/code/modules/clothing/suits/miscellaneous.dm index 6dfcd72c10..3e72765234 100644 --- a/code/modules/clothing/suits/miscellaneous.dm +++ b/code/modules/clothing/suits/miscellaneous.dm @@ -487,6 +487,17 @@ item_state = "militaryjacket" allowed = list(/obj/item/flashlight, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter, /obj/item/gun/ballistic/automatic/pistol, /obj/item/gun/ballistic/revolver, /obj/item/radio) +/obj/item/clothing/suit/jacket/urbanjacket/polychromic + name = "urban jacket" + desc = "A canvas jacket styled with a fur neck piece, stylish." + icon_state = "urbanjacket" + item_state = "urbanjacket" + allowed = list(/obj/item/flashlight, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter, /obj/item/gun/ballistic/automatic/pistol, /obj/item/gun/ballistic/revolver, /obj/item/radio) + +/obj/item/clothing/suit/jacket/urbanjacket/polychromic/ComponentInitialize() + . = ..() + AddElement(/datum/element/polychromic, list("#3D4C31", "#CBBDAF", "#3B3B3B"), 3) + /obj/item/clothing/suit/jacket/letterman name = "letterman jacket" desc = "A classic brown letterman jacket. Looks pretty hot and heavy." @@ -1023,6 +1034,22 @@ alternate_worn_layer = UNDER_HEAD_LAYER mutantrace_variation = STYLE_DIGITIGRADE|STYLE_NO_ANTHRO_ICON +/obj/item/clothing/suit/toggle/wbreakpoly + name = "polychromic windbreaker" + desc = "Perfect for windy days." + icon_state = "wbreakpoly" + item_state = "wbreakpoly" + +/obj/item/clothing/suit/toggle/wbreakpoly/on_toggle(mob/user) + if(suittoggled) + to_chat(usr, "You zip up [src].") + else + to_chat(usr, "You unzip [src].") + +/obj/item/clothing/suit/toggle/wbreakpoly/polychromic/ComponentInitialize() + . = ..() + AddElement(/datum/element/polychromic, list("#464F65", "#916035", "#474747"), 3) + /obj/item/clothing/suit/flakjack name = "flak jacket" desc = "A dilapidated jacket made of a supposedly bullet-proof material (Hint: It isn't.). Smells faintly of napalm." @@ -1088,6 +1115,36 @@ icon_state = "winterhood_poly" item_state = "winterhood_poly" +/obj/item/clothing/suit/hooded/wintercoat/timidcostume + name = "timid woman hoodie" + desc = "A snug, tight yet warm outfit with belts wrapped around it. Looks to be made of polychromic materials." + icon_state = "timidwoman" + item_state = "timidwoman" + hoodtype = /obj/item/clothing/head/hooded/winterhood/timidcostume + +/obj/item/clothing/suit/hooded/wintercoat/timidcostume/ComponentInitialize() + . = ..() + AddElement(/datum/element/polychromic, list("#EB0C07", "#5E2400", "#CEA100"), 3) + +/obj/item/clothing/head/hooded/winterhood/timidcostume + name = "timid woman hood" + desc = "A hood attached to the hoodie." + icon_state = "timidwoman" + item_state = "timidwoman" + +/obj/item/clothing/suit/hooded/wintercoat/timidcostume/man + name = "timid man hoodie" + desc = "A snug, tight yet warm outfit a belt wrapped around it. Looks to be made of polychromic materials." + icon_state = "timidman" + item_state = "timidman" + hoodtype = /obj/item/clothing/head/hooded/winterhood/timidcostume/man + +/obj/item/clothing/head/hooded/winterhood/timidcostume/man + name = "timid man hood" + icon_state = "timidman" + item_state = "timidman" + + /obj/item/clothing/suit/striped_sweater name = "striped sweater" desc = "Reminds you of someone, but you just can't put your finger on it..." diff --git a/code/modules/clothing/suits/toggles.dm b/code/modules/clothing/suits/toggles.dm index f496468371..98d6809d64 100644 --- a/code/modules/clothing/suits/toggles.dm +++ b/code/modules/clothing/suits/toggles.dm @@ -58,6 +58,12 @@ RemoveHood() /obj/item/clothing/suit/hooded/proc/ToggleHood() + if(!hood) + to_chat(loc, "[src] seems to be missing its hood..") + return + if(atom_colours) + hood.atom_colours = atom_colours.Copy() + hood.update_atom_colour() if(!suittoggled) if(ishuman(src.loc)) var/mob/living/carbon/human/H = src.loc @@ -104,6 +110,9 @@ suit_toggle(user) return TRUE +/obj/item/clothing/suit/toggle/proc/on_toggle(mob/user) // override this, not suit_toggle, which does checks + to_chat(usr, "You toggle [src]'s [togglename].") + /obj/item/clothing/suit/toggle/ui_action_click() suit_toggle() @@ -113,7 +122,7 @@ if(!can_use(usr)) return 0 - to_chat(usr, "You toggle [src]'s [togglename].") + on_toggle(usr) if(src.suittoggled) src.icon_state = "[initial(icon_state)]" src.suittoggled = FALSE @@ -191,7 +200,11 @@ if(!helmettype) return if(!helmet) + to_chat(H, "[src] seems to be missing its helmet..") return + if(atom_colours) + helmet.atom_colours = atom_colours.Copy() + helmet.update_atom_colour() if(!suittoggled) if(ishuman(src.loc)) if(H.wear_suit != src) diff --git a/code/modules/clothing/under/_under.dm b/code/modules/clothing/under/_under.dm index e617d2d57b..3207a5842f 100644 --- a/code/modules/clothing/under/_under.dm +++ b/code/modules/clothing/under/_under.dm @@ -32,6 +32,9 @@ /obj/item/clothing/under/attackby(obj/item/I, mob/user, params) if((has_sensor == BROKEN_SENSORS) && istype(I, /obj/item/stack/cable_coil)) + if(damaged_clothes) + to_chat(user,"You should repair the damage done to [src] first.") + return 0 var/obj/item/stack/cable_coil/C = I I.use_tool(src, user, 0, 1) has_sensor = HAS_SENSORS diff --git a/code/modules/clothing/under/accessories.dm b/code/modules/clothing/under/accessories.dm index ee7e4c48e1..7f28b88ad6 100644 --- a/code/modules/clothing/under/accessories.dm +++ b/code/modules/clothing/under/accessories.dm @@ -79,13 +79,175 @@ if(initial(above_suit)) . += "\The [src] can be worn above or below your suit. Alt-click to toggle." +////////////// +//Waistcoats// +////////////// + /obj/item/clothing/accessory/waistcoat - name = "waistcoat" + name = "black waistcoat" desc = "For some classy, murderous fun." icon_state = "waistcoat" item_state = "waistcoat" minimize_when_attached = FALSE +/obj/item/clothing/accessory/waistcoat/red + name = "red waistcoat" + icon_state = "waistcoat_red" + item_state = "waistcoat_red" + +/obj/item/clothing/accessory/waistcoat/grey + name = "grey waistcoat" + icon_state = "waistcoat_grey" + item_state = "waistcoat_grey" + +/obj/item/clothing/accessory/waistcoat/brown + name = "red waistcoat" + icon_state = "waistcoat_brown" + item_state = "waistcoat_brown" + +/obj/item/clothing/accessory/waistcoat/sweatervest + name = "black sweatervest" + icon_state = "sweatervest" + item_state = "sweatervest" + +/obj/item/clothing/accessory/waistcoat/sweatervest/blue + name = "blue sweatervest" + icon_state = "sweatervest_blue" + item_state = "sweatervest_blue" + +/obj/item/clothing/accessory/waistcoat/sweatervest/red + name = "red sweatervest" + icon_state = "sweatervest_red" + item_state = "sweatervest_red" + +//////////// +//Sweaters// +//////////// + +/obj/item/clothing/accessory/sweater + name = "grey sweater" + desc = "Nicely comfy and warm!" + icon_state = "sweater" + item_state = "sweater" + minimize_when_attached = FALSE + +/obj/item/clothing/accessory/sweater/pink + name = "pink sweater" + icon_state = "sweater_pink" + item_state = "sweater_pink" + +/obj/item/clothing/accessory/sweater/heart + name = "heart sweater" + icon_state = "sweater_heart" + item_state = "sweater_heart" + +/obj/item/clothing/accessory/sweater/blue + name = "blue sweater" + icon_state = "sweater_blue" + item_state = "sweater_blue" + +/obj/item/clothing/accessory/sweater/nt + name = "nanotrasen sweater" + icon_state = "sweater_nt" + item_state = "sweater_nt" + +/obj/item/clothing/accessory/sweater/mint + name = "mint sweater" + icon_state = "sweater_mint" + item_state = "sweater_mint" + +/obj/item/clothing/accessory/sweater/shoulderless + name = "shoulderless sweater" + icon_state = "sweater_shoulderless" + item_state = "sweater_shoulderless" + +/obj/item/clothing/accessory/sweater/uglyxmas + name = "ugly xmas sweater" + icon_state = "sweater_uglyxmas" + item_state = "sweater_uglyxmas" + +/obj/item/clothing/accessory/sweater/flower + name = "flower sweater" + icon_state = "sweater_flower" + item_state = "sweater_flower" + +//////////////// +//Suit Jackets// +//////////////// + +/obj/item/clothing/accessory/suitjacket + name = "tan suit jacket" + desc = "For those times when you have to attend a fancy business meeting without wearing your pants." + icon_state = "jacket_tan" + item_state = "jacket_tan" + minimize_when_attached = FALSE + +/obj/item/clothing/accessory/suitjacket/charcoal + name = "charcoal suit jacket" + icon_state = "jacket_charcoal" + item_state = "jacket_charcoal" + +/obj/item/clothing/accessory/suitjacket/navy + name = "navy suit jacket" + icon_state = "jacket_navy" + item_state = "jacket_navy" + +/obj/item/clothing/accessory/suitjacket/burgundy + name = "burgundy suit jacket" + icon_state = "jacket_burgundy" + item_state = "jacket_burgundy" + +/obj/item/clothing/accessory/suitjacket/checkered + name = "checkered suit jacket" + icon_state = "jacket_checkered" + item_state = "jacket_checkered" + +/////////////////////// +//Tactical Turtlnecks// +/////////////////////// + +/obj/item/clothing/accessory/turtleneck + name = "black turtleneck" + desc = "Extra cool. Extra fool." + icon_state = "turtleneck" + item_state = "turtleneck" + minimize_when_attached = FALSE + +/obj/item/clothing/accessory/turtleneck/red + name = "red turtleneck" + icon_state = "turtleneck_red" + item_state = "turtleneck_red" + +/obj/item/clothing/accessory/turtleneck/comfy + name = "comfy turtleneck" + icon_state = "turtleneck_comfy" + item_state = "turtleneck_comfy" + +/obj/item/clothing/accessory/turtleneck/tactifool + name = "black sweaterneck" + desc = "Extra fool. Extra cool." + icon_state = "tactifool" + item_state = "tactifool" + +/obj/item/clothing/accessory/turtleneck/tactifool/green + name = "green sweaterneck" + icon_state = "tactifool_green" + item_state = "tactifool_green" + +/obj/item/clothing/accessory/turtleneck/tactifool/blue + name = "blue sweaterneck" + icon_state = "tactifool_blue" + item_state = "tactifool_blue" + +/obj/item/clothing/accessory/turtleneck/tactifool/syndicate + name = "tactifool sweaterneck" + icon_state = "tactifool_syndicate" + item_state = "tactifool_syndicate" + +///////////////// +//Miscellaneous// +///////////////// + /obj/item/clothing/accessory/maidapron name = "maid apron" desc = "The best part of a maid costume." diff --git a/code/modules/clothing/under/pants.dm b/code/modules/clothing/under/pants.dm index c93cefeae9..a76bf41e62 100644 --- a/code/modules/clothing/under/pants.dm +++ b/code/modules/clothing/under/pants.dm @@ -46,6 +46,17 @@ desc = "Some tan pants. You look like a white collar worker with these on." icon_state = "tanpants" +/obj/item/clothing/under/pants/polypants/polychromic + name = "polychromic pants" + desc = "Some stylish pair of pants made from polychrome." + icon_state = "polypants" + item_state = "polypants" + var/list/poly_colors = list("#75634F", "#3D3D3D", "#575757") + +/obj/item/clothing/under/pants/polypants/polychromic/ComponentInitialize() + . = ..() + AddElement(/datum/element/polychromic, poly_colors, 3) + /obj/item/clothing/under/pants/track name = "track pants" desc = "A pair of track pants, for the athletic." diff --git a/code/modules/clothing/under/suits.dm b/code/modules/clothing/under/suits.dm index 87e6e94b6b..7f0ecf3d70 100644 --- a/code/modules/clothing/under/suits.dm +++ b/code/modules/clothing/under/suits.dm @@ -91,6 +91,12 @@ icon_state = "tan_suit" item_state = "tan_suit" +/obj/item/clothing/under/suit/charismatic_suit + name = "charismatic suit" + desc = "Luck is for losers, baby." + icon_state = "charismatic_suit" + item_state = "charismatic_suit" + /obj/item/clothing/under/suit/white name = "white suit" desc = "A white suit and jacket with a blue shirt. You wanna play rough? OKAY!" @@ -132,3 +138,27 @@ icon_state = "greyturtle" item_state = "greyturtle" can_adjust = FALSE + +/obj/item/clothing/under/suit/turtle/purple + name = "purple turtleneck" + icon_state = "turtle_sci" + item_state = "turtle_sci" + can_adjust = FALSE + +/obj/item/clothing/under/suit/turtle/orange + name = "orange turtleneck" + icon_state = "turtle_eng" + item_state = "turtle_eng" + can_adjust = FALSE + +/obj/item/clothing/under/suit/turtle/red + name = "red turtleneck" + icon_state = "turtle_sec" + item_state = "turtle_sec" + can_adjust = FALSE + +/obj/item/clothing/under/suit/turtle/blue + name = "blue turtleneck" + icon_state = "turtle_med" + item_state = "turtle_med" + can_adjust = FALSE diff --git a/code/modules/events/_event.dm b/code/modules/events/_event.dm index 40ce543d31..10f8c60386 100644 --- a/code/modules/events/_event.dm +++ b/code/modules/events/_event.dm @@ -92,7 +92,7 @@ log_admin_private("[key_name(usr)] cancelled event [name].") SSblackbox.record_feedback("tally", "event_admin_cancelled", 1, typepath) -/datum/round_event_control/proc/runEvent() +/datum/round_event_control/proc/runEvent(random = FALSE) var/datum/round_event/E = new typepath() E.current_players = get_active_player_count(alive_check = 1, afk_check = 1, human_check = 1) E.control = src diff --git a/code/modules/events/anomaly_bluespace.dm b/code/modules/events/anomaly_bluespace.dm index 7f0dedaab6..70d6b00fa4 100644 --- a/code/modules/events/anomaly_bluespace.dm +++ b/code/modules/events/anomaly_bluespace.dm @@ -11,7 +11,4 @@ anomaly_path = /obj/effect/anomaly/bluespace /datum/round_event/anomaly/anomaly_bluespace/announce(fake) - if(prob(90)) - priority_announce("Unstable bluespace anomaly detected on long range scanners. Expected location: [impact_area.name].", "Anomaly Alert") - else - print_command_report("Unstable bluespace anomaly detected on long range scanners. Expected location: [impact_area.name].", "Unstable bluespace anomaly") + priority_announce("Unstable bluespace anomaly detected on long range scanners. Expected location: [impact_area.name].", "Anomaly Alert") diff --git a/code/modules/events/anomaly_flux.dm b/code/modules/events/anomaly_flux.dm index 8047976330..0ba347af57 100644 --- a/code/modules/events/anomaly_flux.dm +++ b/code/modules/events/anomaly_flux.dm @@ -12,7 +12,5 @@ anomaly_path = /obj/effect/anomaly/flux /datum/round_event/anomaly/anomaly_flux/announce(fake) - if(prob(90)) - priority_announce("Localized hyper-energetic flux wave detected on long range scanners. Expected location: [impact_area.name].", "Anomaly Alert") - else - print_command_report("Localized hyper-energetic flux wave detected on long range scanners. Expected location: [impact_area.name].","Localized hyper-energetic flux wave") + priority_announce("Localized hyper-energetic flux wave detected on long range scanners. Expected location: [impact_area.name].", "Anomaly Alert") + diff --git a/code/modules/events/anomaly_grav.dm b/code/modules/events/anomaly_grav.dm index 7d2bb33889..01e70c290c 100644 --- a/code/modules/events/anomaly_grav.dm +++ b/code/modules/events/anomaly_grav.dm @@ -12,7 +12,4 @@ anomaly_path = /obj/effect/anomaly/grav /datum/round_event/anomaly/anomaly_grav/announce(fake) - if(prob(90)) - priority_announce("Gravitational anomaly detected on long range scanners. Expected location: [impact_area.name].", "Anomaly Alert") - else - print_command_report("Gravitational anomaly detected on long range scanners. Expected location: [impact_area.name].", "Gravitational anomaly") + priority_announce("Gravitational anomaly detected on long range scanners. Expected location: [impact_area.name].", "Anomaly Alert") diff --git a/code/modules/events/anomaly_pyro.dm b/code/modules/events/anomaly_pyro.dm index 350c8fc946..29c6e15d28 100644 --- a/code/modules/events/anomaly_pyro.dm +++ b/code/modules/events/anomaly_pyro.dm @@ -11,7 +11,4 @@ anomaly_path = /obj/effect/anomaly/pyro /datum/round_event/anomaly/anomaly_pyro/announce(fake) - if(prob(90)) - priority_announce("Pyroclastic anomaly detected on long range scanners. Expected location: [impact_area.name].", "Anomaly Alert") - else - print_command_report("Pyroclastic anomaly detected on long range scanners. Expected location: [impact_area.name].", "Pyroclastic anomaly") + priority_announce("Pyroclastic anomaly detected on long range scanners. Expected location: [impact_area.name].", "Anomaly Alert") diff --git a/code/modules/events/anomaly_vortex.dm b/code/modules/events/anomaly_vortex.dm index e2a4ceadf3..6a17a47d2b 100644 --- a/code/modules/events/anomaly_vortex.dm +++ b/code/modules/events/anomaly_vortex.dm @@ -12,7 +12,4 @@ anomaly_path = /obj/effect/anomaly/bhole /datum/round_event/anomaly/anomaly_vortex/announce(fake) - if(prob(90)) - priority_announce("Localized high-intensity vortex anomaly detected on long range scanners. Expected location: [impact_area.name]", "Anomaly Alert") - else - print_command_report("Localized high-intensity vortex anomaly detected on long range scanners. Expected location: [impact_area.name].","Vortex anomaly") + priority_announce("Localized high-intensity vortex anomaly detected on long range scanners. Expected location: [impact_area.name]", "Anomaly Alert") diff --git a/code/modules/events/brain_trauma.dm b/code/modules/events/brain_trauma.dm index 2c3f92ee87..3d39f8720a 100644 --- a/code/modules/events/brain_trauma.dm +++ b/code/modules/events/brain_trauma.dm @@ -27,6 +27,7 @@ if(!is_station_level(H.z)) continue traumatize(H) + announce_to_ghosts(H) break /datum/round_event/brain_trauma/proc/traumatize(mob/living/carbon/human/H) diff --git a/code/modules/events/brand_intelligence.dm b/code/modules/events/brand_intelligence.dm index 7c55bbfcd1..7d4ea66d30 100644 --- a/code/modules/events/brand_intelligence.dm +++ b/code/modules/events/brand_intelligence.dm @@ -35,10 +35,7 @@ source = initial(example.name) else if(originMachine) source = originMachine.name - if(prob(50)) - priority_announce("Rampant brand intelligence has been detected aboard [station_name()]. Please stand by. The origin is believed to be \a [source].", "Machine Learning Alert") - else - print_command_report("Rampant brand intelligence has been detected aboard [station_name()]. Please stand by. The origin is believed to be \a [source].", "Rampant brand intelligence") + priority_announce("Rampant brand intelligence has been detected aboard [station_name()]. Please stand by. The origin is believed to be \a [source].", "Machine Learning Alert") /datum/round_event/brand_intelligence/start() for(var/obj/machinery/vending/V in GLOB.machines) diff --git a/code/modules/events/holiday/vday.dm b/code/modules/events/holiday/vday.dm index 1da03623e6..df00f873b5 100644 --- a/code/modules/events/holiday/vday.dm +++ b/code/modules/events/holiday/vday.dm @@ -21,27 +21,6 @@ new /obj/item/reagent_containers/food/snacks/candyheart(B) new /obj/item/storage/fancy/heart_box(B) - var/list/valentines = list() - for(var/mob/living/M in GLOB.player_list) - if(!M.stat && M.client && M.mind && !HAS_TRAIT(M, TRAIT_NO_MIDROUND_ANTAG)) - valentines |= M - - - while(valentines.len) - var/mob/living/L = pick_n_take(valentines) - if(valentines.len) - var/mob/living/date = pick_n_take(valentines) - - - forge_valentines_objective(L, date) - forge_valentines_objective(date, L) - - if(valentines.len && prob(4)) - var/mob/living/notgoodenough = pick_n_take(valentines) - forge_valentines_objective(notgoodenough, date) - else - L.mind.add_antag_datum(/datum/antagonist/heartbreaker) - /proc/forge_valentines_objective(mob/living/lover,mob/living/date,var/chemLove = FALSE) lover.mind.special_role = "valentine" if (chemLove == TRUE) diff --git a/code/modules/events/immovable_rod.dm b/code/modules/events/immovable_rod.dm index 06318df4f5..a9ee0f5412 100644 --- a/code/modules/events/immovable_rod.dm +++ b/code/modules/events/immovable_rod.dm @@ -144,22 +144,24 @@ In my current plan for it, 'solid' will be defined as anything with density == 1 if(L && (L.density || prob(10))) L.ex_act(EXPLODE_HEAVY) -obj/effect/immovablerod/on_attack_hand(mob/living/user, act_intent = user.a_intent, unarmed_attack_flags) - if(ishuman(user)) - var/mob/living/carbon/human/U = user - if(U.job in list("Research Director")) - playsound(src, 'sound/effects/meteorimpact.ogg', 100, 1) - for(var/mob/M in urange(8, src)) - if(!M.stat) - shake_camera(M, 2, 3) - if(wizard) - U.visible_message("[src] transforms into [wizard] as [U] suplexes them!", "As you grab [src], it suddenly turns into [wizard] as you suplex them!") - to_chat(wizard, "You're suddenly jolted out of rod-form as [U] somehow manages to grab you, slamming you into the ground!") - wizard.Stun(60) - wizard.apply_damage(25, BRUTE) - qdel(src) - else - U.visible_message("[U] suplexes [src] into the ground!", "You suplex [src] into the ground!") - new /obj/structure/festivus/anchored(drop_location()) - new /obj/effect/anomaly/flux(drop_location()) - qdel(src) +/obj/effect/immovablerod/on_attack_hand(mob/living/user, act_intent = user.a_intent, unarmed_attack_flags) + if(!ishuman(user)) + return + var/mob/living/carbon/human/U = user + if(U.job in list("Research Director")) + playsound(src, 'sound/effects/meteorimpact.ogg', 100, TRUE) + for(var/mob/M in urange(8, src)) + if(!M.stat) + shake_camera(M, 2, 3) + if(wizard) + U.visible_message("[src] transforms into [wizard] as [U] suplexes them!", "As you grab [src], it suddenly turns into [wizard] as you suplex them!") + to_chat(wizard, "You're suddenly jolted out of rod-form as [U] somehow manages to grab you, slamming you into the ground!") + wizard.Stun(60) + wizard.apply_damage(25, BRUTE) + qdel(src) + else + U.client.give_award(/datum/award/achievement/misc/feat_of_strength, U) //rod-form wizards would probably make this a lot easier to get so keep it to regular rods only + U.visible_message("[U] suplexes [src] into the ground!", "You suplex [src] into the ground!") + new /obj/structure/festivus/anchored(drop_location()) + new /obj/effect/anomaly/flux(drop_location()) + qdel(src) diff --git a/code/modules/events/pirates.dm b/code/modules/events/pirates.dm index 8bada6da77..5a5df41163 100644 --- a/code/modules/events/pirates.dm +++ b/code/modules/events/pirates.dm @@ -267,8 +267,8 @@ var/sending_state = "lpad-beam" var/cargo_hold_id -/obj/machinery/piratepad/multitool_act(mob/living/user, obj/item/multitool/I) - if (istype(I)) +/obj/machinery/piratepad/multitool_act(mob/living/user, obj/item/I) + if(I.tool_behaviour == TOOL_MULTITOOL) to_chat(user, "You register [src] in [I]s buffer.") I.buffer = src return TRUE @@ -291,8 +291,8 @@ ..() return INITIALIZE_HINT_LATELOAD -/obj/machinery/computer/piratepad_control/multitool_act(mob/living/user, obj/item/multitool/I) - if (istype(I) && istype(I.buffer,/obj/machinery/piratepad)) +/obj/machinery/computer/piratepad_control/multitool_act(mob/living/user, obj/item/I) + if(I.tool_behaviour == TOOL_MULTITOOL && istype(I.buffer,/obj/machinery/piratepad)) to_chat(user, "You link [src] with [I.buffer] in [I] buffer.") pad = I.buffer return TRUE diff --git a/code/modules/events/shuttle_loan.dm b/code/modules/events/shuttle_loan.dm index bf9f25cb04..347162d9c5 100644 --- a/code/modules/events/shuttle_loan.dm +++ b/code/modules/events/shuttle_loan.dm @@ -45,7 +45,7 @@ if(ANTIDOTE_NEEDED) message = "Cargo: Your station has been chosen for an epidemiological research project. Send us your cargo shuttle to receive your research samples." title = "CentCom Research Initiatives" - if (PIZZA_DELIVERY) + if(PIZZA_DELIVERY) message = "Cargo: It looks like a neighbouring station accidentally delivered their pizza to you instead." title = "CentCom Spacepizza Division" if(ITS_HIP_TO) @@ -57,7 +57,7 @@ title = "CentCom Security Division" bonus_points = 45000 //If you mess up, people die and the shuttle gets turned into swiss cheese if(DELTA_CRATES) - message = "Cargo: We have discovered a warehouse of DELTA locked crates, we cant store any more of them at CC can you take them for us?." + message = "Cargo: We have discovered a warehouse of DELTA locked crates. We can't store any more of them at CC, can you take them for us?" title = "CentCom Security Division" bonus_points = 25000 //If you mess up, people die and the shuttle gets turned into swiss cheese if(prob(50)) diff --git a/code/modules/events/spacevine.dm b/code/modules/events/spacevine.dm index 7cf11848e8..c8679447b8 100644 --- a/code/modules/events/spacevine.dm +++ b/code/modules/events/spacevine.dm @@ -3,7 +3,7 @@ typepath = /datum/round_event/spacevine weight = 15 max_occurrences = 3 - min_players = 10 + min_players = 20 /datum/round_event/spacevine fakeable = FALSE @@ -340,6 +340,7 @@ playsound(src.loc, 'sound/items/welder.ogg', 100, TRUE) /obj/structure/spacevine/Crossed(atom/movable/AM) + . = ..() if(!isliving(AM)) return for(var/datum/spacevine_mutation/SM in mutations) diff --git a/code/modules/events/supermatter_surge.dm b/code/modules/events/supermatter_surge.dm new file mode 100644 index 0000000000..d54fc4dcd2 --- /dev/null +++ b/code/modules/events/supermatter_surge.dm @@ -0,0 +1,23 @@ +/datum/round_event_control/supermatter_surge + name = "Supermatter Surge" + typepath = /datum/round_event/supermatter_surge + weight = 20 + max_occurrences = 4 + earliest_start = 10 MINUTES + +/datum/round_event_control/supermatter_surge/canSpawnEvent() + if(GLOB.main_supermatter_engine?.has_been_powered) + return ..() + +/datum/round_event/supermatter_surge + var/power = 2000 + +/datum/round_event/supermatter_surge/setup() + power = rand(200,4000) + +/datum/round_event/supermatter_surge/announce() + if(power > 800 || prob(round(power/8))) + priority_announce("Class [round(power/500) + 1] supermatter surge detected. Intervention may be required.", "Anomaly Alert") + +/datum/round_event/supermatter_surge/start() + GLOB.main_supermatter_engine.matter_power += power diff --git a/code/modules/events/supernova.dm b/code/modules/events/supernova.dm new file mode 100644 index 0000000000..9766ab6212 --- /dev/null +++ b/code/modules/events/supernova.dm @@ -0,0 +1,67 @@ +/datum/round_event_control/supernova + name = "Supernova" + typepath = /datum/round_event/supernova + weight = 10 + max_occurrences = 2 + min_players = 2 + +/datum/round_event/supernova + announceWhen = 40 + startWhen = 1 + endWhen = 300 + var/power = 1 + var/datum/sun/supernova + var/storm_count = 0 + +/datum/round_event/supernova/setup() + announceWhen = rand(4, 60) + supernova = new + SSsun.suns += supernova + if(prob(50)) + power = rand(5,100) / 100 + else + power = rand(5,5000) / 100 + supernova.azimuth = rand(0, 359) + supernova.power_mod = 0 + +/datum/round_event/supernova/announce() + var/message = "Our tachyon-doppler array has detected a supernova in your vicinity. Peak flux from the supernova estimated to be [round(power,0.1)] times current solar flux. [power > 4 ? "Short burts of radiation may be possible, so please prepare accordingly." : ""]" + if(prob(power * 25)) + priority_announce(message) + else + print_command_report(message) + + +/datum/round_event/supernova/start() + supernova.power_mod = 0.00000002 * power + var/explosion_size = rand(1000000000, 999999999) + var/turf/epicenter = get_turf_in_angle(supernova.azimuth, SSmapping.get_station_center(), world.maxx / 2) + for(var/array in GLOB.doppler_arrays) + var/obj/machinery/doppler_array/A = array + A.sense_explosion(epicenter, explosion_size/2, explosion_size, 0, 107000000 / power, explosion_size/2, explosion_size, 0) + if(power > 1 && SSticker.mode.bloodsucker_sunlight?.time_til_cycle > 90) + var/obj/effect/sunlight/sucker_light = SSticker.mode.bloodsucker_sunlight + sucker_light.time_til_cycle = 90 + sucker_light.warn_daylight(1,"A supernova will bombard the station with dangerous UV in [90 / 60] minutes. Prepare to seek cover in a coffin or closet.") + sucker_light.give_home_power() + +/datum/round_event/supernova/tick() + var/midpoint = (endWhen-startWhen)/2 + switch(activeFor) + if(startWhen to midpoint) + supernova.power_mod = min(supernova.power_mod*1.2, power) + if(endWhen-10 to endWhen) + supernova.power_mod /= 4 + if(prob(round(supernova.power_mod / 2)) && storm_count < 3 && !SSweather.get_weather_by_type(/datum/weather/rad_storm)) + SSweather.run_weather(/datum/weather/rad_storm/supernova) + storm_count++ + +/datum/round_event/supernova/end() + SSsun.suns -= supernova + qdel(supernova) + +/datum/weather/rad_storm/supernova + weather_duration_lower = 50 + weather_duration_lower = 100 + telegraph_duration = 100 + radiation_intensity = 50 diff --git a/code/modules/events/travelling_trader.dm b/code/modules/events/travelling_trader.dm index eeb1acdba5..2f7b9dbea5 100644 --- a/code/modules/events/travelling_trader.dm +++ b/code/modules/events/travelling_trader.dm @@ -25,14 +25,14 @@ var/datum/effect_system/smoke_spread/smoke = new smoke.set_up(1, spawn_location) smoke.start() - trader.visible_message("[src] suddenly appears in a puff of smoke!") + trader.visible_message("[trader] suddenly appears in a puff of smoke!") /datum/round_event/travelling_trader/announce(fake) priority_announce("A mysterious figure has been detected on sensors at [get_area(spawn_location)]", "Mysterious Figure") /datum/round_event/travelling_trader/end() - if(trader) - trader.visible_message("The [src] has given up on waiting!") + if(trader) // the /datum/round_event/travelling_trader has given up on waiting! + trader.visible_message("The [trader] has given up on waiting!") qdel(trader) //the actual trader mob @@ -99,7 +99,7 @@ new reward(get_turf(src)) /mob/living/carbon/human/dummy/travelling_trader/Initialize() - ..() + . = ..() // return a hint you fuck add_atom_colour("#570d6b", FIXED_COLOUR_PRIORITY) //make them purple (otherworldly!) set_light(1, -0.7, "#AAD84B") ADD_TRAIT(src,TRAIT_PIERCEIMMUNE, "trader_pierce_immune") //don't let people take their blood @@ -188,18 +188,18 @@ /mob/living/simple_animal/hostile/netherworld/blankbody = 1, /mob/living/simple_animal/hostile/retaliate/goose = 1) -mob/living/carbon/human/dummy/travelling_trader/animal_hunter/Initialize() +/mob/living/carbon/human/dummy/travelling_trader/animal_hunter/Initialize() + . = ..() acceptance_speech = pick(list("This lifeform shall make for a great stew, thank you.", "This lifeform shall be of a true use to our cause, thank you.", "The lifeform is adequate. Goodbye.", "This lifeform shall make a great addition to my collection.")) - ..() -/mob/living/carbon/human/dummy/travelling_trader/animal_hunter/check_item(var/obj/item/supplied_item) //item is likely to be in contents of whats supplied +/mob/living/carbon/human/dummy/travelling_trader/animal_hunter/check_item(obj/item/supplied_item) //item is likely to be in contents of whats supplied for(var/atom/something in supplied_item.contents) if(istype(something, requested_item)) qdel(something) //typically things holding mobs release the mob when the container is deleted, so delete the mob first here return TRUE return FALSE -/mob/living/carbon/human/dummy/travelling_trader/animal_hunter/give_reward(var/mob/giver) //the reward is actually given in a jar, because releasing it onto the station might be a bad idea +/mob/living/carbon/human/dummy/travelling_trader/animal_hunter/give_reward(mob/giver) //the reward is actually given in a jar, because releasing it onto the station might be a bad idea var/obj/item/pet_carrier/bluespace/jar = new(get_turf(src)) var/chosen_animal = pickweight(possible_rewards) var/mob/living/new_animal = new chosen_animal(jar) @@ -223,6 +223,7 @@ mob/living/carbon/human/dummy/travelling_trader/animal_hunter/Initialize() /obj/structure/reagent_dispensers/keg/quintuple_sec = 3) /mob/living/carbon/human/dummy/travelling_trader/bartender/Initialize() //pick a subtype of ethanol that isn't found in the default set of the booze dispensers reagents + . = ..() // RETURN A HINT. requested_item = pick(subtypesof(/datum/reagent/consumable/ethanol) - list(/datum/reagent/consumable/ethanol/beer, /datum/reagent/consumable/ethanol/kahlua, /datum/reagent/consumable/ethanol/whiskey, @@ -242,7 +243,6 @@ mob/living/carbon/human/dummy/travelling_trader/animal_hunter/Initialize() /datum/reagent/consumable/ethanol/triple_sec, /datum/reagent/consumable/ethanol/sake, /datum/reagent/consumable/ethanol/applejack)) - ..() /mob/living/carbon/human/dummy/travelling_trader/bartender/check_item(var/obj/item/supplied_item) //you need to check its reagents if(istype(supplied_item, /obj/item/reagent_containers)) diff --git a/code/modules/fields/fields.dm b/code/modules/fields/fields.dm index 8710282021..cb8d3e8dcf 100644 --- a/code/modules/fields/fields.dm +++ b/code/modules/fields/fields.dm @@ -16,7 +16,7 @@ if(!F.check_variables() && !override_checks) QDEL_NULL(F) if(start_field && (F || override_checks)) - F.Initialize() + F.begin_field() return F /datum/proximity_monitor/advanced @@ -78,11 +78,11 @@ /datum/proximity_monitor/advanced/proc/process_edge_turf(turf/T) -/datum/proximity_monitor/advanced/New() +/datum/proximity_monitor/advanced/New(atom/_host, range, _ignore_if_not_on_turf = TRUE) if(requires_processing) START_PROCESSING(SSfields, src) -/datum/proximity_monitor/advanced/proc/Initialize() +/datum/proximity_monitor/advanced/proc/begin_field() setup_field() post_setup_field() @@ -154,7 +154,7 @@ var/atom/_host = host var/atom/new_host_loc = _host.loc if(last_host_loc != new_host_loc) - recalculate_field() + INVOKE_ASYNC(src, .proc/recalculate_field) /datum/proximity_monitor/advanced/proc/post_setup_field() @@ -302,7 +302,7 @@ /obj/item/multitool/field_debug/attack_self(mob/user) operating = !operating - to_chat(user, "You turn [src] [operating? "on":"off"].") + to_chat(user, "You turn [src] [operating? "on":"off"].") UnregisterSignal(listeningTo, COMSIG_MOVABLE_MOVED) listeningTo = null if(!istype(current) && operating) @@ -312,13 +312,15 @@ else if(!operating) QDEL_NULL(current) -/obj/item/multitool/field_debug/dropped(mob/user) +/obj/item/multitool/field_debug/dropped() . = ..() if(listeningTo) UnregisterSignal(listeningTo, COMSIG_MOVABLE_MOVED) listeningTo = null /obj/item/multitool/field_debug/proc/on_mob_move() + SIGNAL_HANDLER + check_turf(get_turf(src)) /obj/item/multitool/field_debug/process() diff --git a/code/modules/fields/timestop.dm b/code/modules/fields/timestop.dm index 56abef8cd6..a96a44a789 100644 --- a/code/modules/fields/timestop.dm +++ b/code/modules/fields/timestop.dm @@ -33,7 +33,7 @@ if(G.summoner && locate(/obj/effect/proc_holder/spell/aoe_turf/timestop) in G.summoner.mind.spell_list) //It would only make sense that a person's stand would also be immune. immune[G] = TRUE if(start) - timestop() + INVOKE_ASYNC(src, .proc/timestop) /obj/effect/timestop/Destroy() qdel(chronofield) @@ -42,7 +42,7 @@ /obj/effect/timestop/proc/timestop() target = get_turf(src) - playsound(src, 'sound/magic/timeparadox2.ogg', 75, 1, -1) + playsound(src, 'sound/magic/timeparadox2.ogg', 75, TRUE, -1) chronofield = make_field(/datum/proximity_monitor/advanced/timestop, list("current_range" = freezerange, "host" = src, "immune" = immune, "check_anti_magic" = check_anti_magic, "check_holy" = check_holy)) QDEL_IN(src, duration) @@ -112,6 +112,8 @@ unfreeze_turf(T) /datum/proximity_monitor/advanced/timestop/proc/unfreeze_atom(atom/movable/A) + SIGNAL_HANDLER + if(A.throwing) unfreeze_throwing(A) if(isliving(A)) @@ -128,12 +130,14 @@ frozen_things -= A global_frozen_atoms -= A + /datum/proximity_monitor/advanced/timestop/proc/freeze_mecha(obj/mecha/M) M.completely_disabled = TRUE /datum/proximity_monitor/advanced/timestop/proc/unfreeze_mecha(obj/mecha/M) M.completely_disabled = FALSE + /datum/proximity_monitor/advanced/timestop/proc/freeze_throwing(atom/movable/AM) var/datum/thrownthing/T = AM.throwing T.paused = TRUE @@ -160,7 +164,7 @@ /datum/proximity_monitor/advanced/timestop/process() for(var/i in frozen_mobs) var/mob/living/m = i - m.Stun(20, 1, 1) + m.Stun(20, ignore_canstun = TRUE) /datum/proximity_monitor/advanced/timestop/setup_field_turf(turf/T) for(var/i in T.contents) @@ -168,6 +172,7 @@ freeze_turf(T) return ..() + /datum/proximity_monitor/advanced/timestop/proc/freeze_projectile(obj/item/projectile/P) P.paused = TRUE @@ -176,18 +181,18 @@ /datum/proximity_monitor/advanced/timestop/proc/freeze_mob(mob/living/L) frozen_mobs += L - L.Stun(20, 1, 1) + L.Stun(20, ignore_canstun = TRUE) ADD_TRAIT(L, TRAIT_MUTE, TIMESTOP_TRAIT) walk(L, 0) //stops them mid pathing even if they're stunimmune if(isanimal(L)) var/mob/living/simple_animal/S = L S.toggle_ai(AI_OFF) - if(ishostile(L)) - var/mob/living/simple_animal/hostile/H = L - H.LoseTarget() + if(ishostile(L)) + var/mob/living/simple_animal/hostile/H = L + H.LoseTarget() /datum/proximity_monitor/advanced/timestop/proc/unfreeze_mob(mob/living/L) - L.AdjustStun(-20, 1, 1) + L.AdjustStun(-20, ignore_canstun = TRUE) REMOVE_TRAIT(L, TRAIT_MUTE, TIMESTOP_TRAIT) frozen_mobs -= L if(isanimal(L)) diff --git a/code/modules/fields/turf_objects.dm b/code/modules/fields/turf_objects.dm index d37036d83c..1af924294a 100644 --- a/code/modules/fields/turf_objects.dm +++ b/code/modules/fields/turf_objects.dm @@ -24,11 +24,12 @@ desc = "Get off my turf!" /obj/effect/abstract/proximity_checker/advanced/field_turf/CanPass(atom/movable/AM, turf/target) + . = ..() if(parent) return parent.field_turf_canpass(AM, src, target) - return TRUE /obj/effect/abstract/proximity_checker/advanced/field_turf/Crossed(atom/movable/AM) + . = ..() if(parent) return parent.field_turf_crossed(AM, src) return TRUE @@ -48,11 +49,12 @@ desc = "Edgy description here." /obj/effect/abstract/proximity_checker/advanced/field_edge/CanPass(atom/movable/AM, turf/target) + . = ..() if(parent) return parent.field_edge_canpass(AM, src, target) - return TRUE /obj/effect/abstract/proximity_checker/advanced/field_edge/Crossed(atom/movable/AM) + . = ..() if(parent) return parent.field_edge_crossed(AM, src) return TRUE diff --git a/code/modules/flufftext/Hallucination.dm b/code/modules/flufftext/Hallucination.dm index 56c96c3559..cc18207a29 100644 --- a/code/modules/flufftext/Hallucination.dm +++ b/code/modules/flufftext/Hallucination.dm @@ -139,10 +139,11 @@ GLOBAL_LIST_INIT(hallucination_list, list( Show() /obj/effect/hallucination/simple/Moved(atom/OldLoc, Dir) + . = ..() Show() /obj/effect/hallucination/simple/Destroy() - if(target && target.client) + if(target?.client) target.client.images.Remove(current_image) active = FALSE return ..() @@ -1093,6 +1094,7 @@ GLOBAL_LIST_INIT(hallucination_list, list( target.client.images += image /obj/effect/hallucination/danger/lava/Crossed(atom/movable/AM) + . = ..() if(AM == target) target.adjustStaminaLoss(20) new /datum/hallucination/fire(target) diff --git a/code/modules/food_and_drinks/drinks/drinks.dm b/code/modules/food_and_drinks/drinks/drinks.dm index 3f51da22b3..6b8de787f0 100644 --- a/code/modules/food_and_drinks/drinks/drinks.dm +++ b/code/modules/food_and_drinks/drinks/drinks.dm @@ -17,10 +17,7 @@ var/isGlass = TRUE //Whether the 'bottle' is made of glass or not so that milk cartons dont shatter when someone gets hit by it /obj/item/reagent_containers/food/drinks/on_reagent_change(changetype) - if (gulp_size < 5) - gulp_size = 5 - else - gulp_size = max(round(reagents.total_volume / 5), 5) + gulp_size = max(round(reagents.total_volume / 5), 5) /obj/item/reagent_containers/food/drinks/attack(mob/living/M, mob/user, def_zone) if(!reagents || !reagents.total_volume) @@ -495,7 +492,9 @@ playsound(user.loc,'sound/weapons/pierce.ogg', rand(10,50), 1) var/obj/item/trash/can/crushed_can = new /obj/item/trash/can(user.loc) crushed_can.icon_state = icon_state - qdel(src) + M.dropItemToGround(src) + M.put_in_active_hand(crushed_can) + return qdel(src) ..() /obj/item/reagent_containers/food/drinks/soda_cans/attack_self(mob/user) @@ -596,7 +595,7 @@ name = "Buzz Fuzz" desc = "The sister drink of Shambler's Juice! Uses real honey, making it a sweet tooth's dream drink. The slogan reads ''A Hive of Flavour'', there's also a label about how it is adddicting." icon_state = "honeysoda_can" - list_reagents = list(/datum/reagent/consumable/buzz_fuzz = 25, /datum/reagent/consumable/honey = 5) + list_reagents = list(/datum/reagent/consumable/buzz_fuzz = 30) foodtype = SUGAR | JUNKFOOD /obj/item/reagent_containers/food/drinks/soda_cans/grey_bull diff --git a/code/modules/food_and_drinks/drinks/drinks/drinkingglass.dm b/code/modules/food_and_drinks/drinks/drinks/drinkingglass.dm index 8e5069925f..9602c1d24e 100644 --- a/code/modules/food_and_drinks/drinks/drinks/drinkingglass.dm +++ b/code/modules/food_and_drinks/drinks/drinks/drinkingglass.dm @@ -52,10 +52,7 @@ /obj/item/reagent_containers/food/drinks/drinkingglass/shotglass/on_reagent_change(changetype) cut_overlays() - if (gulp_size < 15) - gulp_size = 15 - else - gulp_size = max(round(reagents.total_volume / 15), 15) + gulp_size = max(round(reagents.total_volume / 15), 15) if (reagents.reagent_list.len > 0) var/datum/reagent/largest_reagent = reagents.get_master_reagent() diff --git a/code/modules/food_and_drinks/food/snacks_cake.dm b/code/modules/food_and_drinks/food/snacks_cake.dm index be07826a7f..67ac2ee59b 100644 --- a/code/modules/food_and_drinks/food/snacks_cake.dm +++ b/code/modules/food_and_drinks/food/snacks_cake.dm @@ -308,7 +308,7 @@ tastes = list("cake" = 5, "sweetness" = 1, "clouds" = 1) foodtype = GRAIN | DAIRY | SUGAR -obj/item/reagent_containers/food/snacks/store/cake/pound_cake +/obj/item/reagent_containers/food/snacks/store/cake/pound_cake name = "pound cake" desc = "A condensed cake made for filling people up quickly." icon_state = "pound_cake" diff --git a/code/modules/food_and_drinks/food/snacks_meat.dm b/code/modules/food_and_drinks/food/snacks_meat.dm index 05a0da2793..152740f932 100644 --- a/code/modules/food_and_drinks/food/snacks_meat.dm +++ b/code/modules/food_and_drinks/food/snacks_meat.dm @@ -435,7 +435,7 @@ name = "alien drone cube" desc = "Just add water and run!" tastes = list("the jungle" = 1, "acid" = 1) - dried_being = /mob/living/carbon/alien/humanoid/drone + dried_being = /mob/living/simple_animal/hostile/alien/sentinel/cube /obj/item/reagent_containers/food/snacks/cube/goat name = "goat cube" diff --git a/code/modules/food_and_drinks/food/snacks_pie.dm b/code/modules/food_and_drinks/food/snacks_pie.dm index a4287d3d19..5329829637 100644 --- a/code/modules/food_and_drinks/food/snacks_pie.dm +++ b/code/modules/food_and_drinks/food/snacks_pie.dm @@ -54,7 +54,7 @@ H.visible_message("[H] is creamed by [src]!", "You've been creamed by [src]!") playsound(H, "desceration", 50, TRUE) if(!H.is_mouth_covered()) - reagents.trans_to(H, 15, log = TRUE) //Cream pie combat + reagents.trans_to(H, 15, log = "creampie hit") //Cream pie combat if(!H.creamed) // one layer at a time H.add_overlay(creamoverlay) H.creamed = TRUE diff --git a/code/modules/food_and_drinks/kitchen_machinery/deep_fryer.dm b/code/modules/food_and_drinks/kitchen_machinery/deep_fryer.dm index fecc9467a1..ebde504ec0 100644 --- a/code/modules/food_and_drinks/kitchen_machinery/deep_fryer.dm +++ b/code/modules/food_and_drinks/kitchen_machinery/deep_fryer.dm @@ -79,7 +79,7 @@ God bless America. to_chat(user, "There's nothing to dissolve [I] in!") return user.visible_message("[user] drops [I] into [src].", "You dissolve [I] in [src].") - I.reagents.trans_to(src, I.reagents.total_volume) + I.reagents.trans_to(src, I.reagents.total_volume, log = "pill into deep fryer") qdel(I) return if(istype(I,/obj/item/clothing/head/mob_holder)) diff --git a/code/modules/food_and_drinks/kitchen_machinery/microwave.dm b/code/modules/food_and_drinks/kitchen_machinery/microwave.dm index 6bdf3cff8e..5954788d5c 100644 --- a/code/modules/food_and_drinks/kitchen_machinery/microwave.dm +++ b/code/modules/food_and_drinks/kitchen_machinery/microwave.dm @@ -151,10 +151,13 @@ to_chat(user, "You need more space cleaner!") return TRUE - if(istype(O, /obj/item/soap)) - var/obj/item/soap/P = O + if(istype(O, /obj/item/soap) || istype(O, /obj/item/reagent_containers/rag)) + var/cleanspeed = 50 + if(istype(O, /obj/item/soap)) + var/obj/item/soap/used_soap = O + cleanspeed = used_soap.cleanspeed user.visible_message("[user] starts to clean \the [src].", "You start to clean \the [src]...") - if(do_after(user, P.cleanspeed, target = src)) + if(do_after(user, cleanspeed, target = src)) user.visible_message("[user] has cleaned \the [src].", "You clean \the [src].") dirty = 0 update_icon() diff --git a/code/modules/food_and_drinks/kitchen_machinery/processor.dm b/code/modules/food_and_drinks/kitchen_machinery/processor.dm index 9a3df0a92b..ef2676fe83 100644 --- a/code/modules/food_and_drinks/kitchen_machinery/processor.dm +++ b/code/modules/food_and_drinks/kitchen_machinery/processor.dm @@ -156,11 +156,7 @@ /obj/machinery/processor/slime name = "slime processor" desc = "An industrial grinder with a sticker saying appropriated for science department. Keep hands clear of intake area while operating." - -/obj/machinery/processor/slime/Initialize() - . = ..() - var/obj/item/circuitboard/machine/B = new /obj/item/circuitboard/machine/processor/slime(null) - B.apply_default_parts(src) + circuit = /obj/item/circuitboard/machine/processor/slime /obj/machinery/processor/slime/adjust_item_drop_location(atom/movable/AM) var/static/list/slimecores = subtypesof(/obj/item/slime_extract) diff --git a/code/modules/food_and_drinks/kitchen_machinery/smartfridge.dm b/code/modules/food_and_drinks/kitchen_machinery/smartfridge.dm index 35fa40e15d..f97ef17364 100644 --- a/code/modules/food_and_drinks/kitchen_machinery/smartfridge.dm +++ b/code/modules/food_and_drinks/kitchen_machinery/smartfridge.dm @@ -13,6 +13,7 @@ active_power_usage = 100 circuit = /obj/item/circuitboard/machine/smartfridge + var/base_build_path = /obj/machinery/smartfridge ///What path boards used to construct it should build into when dropped. Needed so we don't accidentally have them build variants with items preloaded in them. var/max_n_of_items = 1500 var/allow_ai_retrieve = FALSE var/list/initial_contents @@ -43,7 +44,7 @@ SSvis_overlays.remove_vis_overlay(src, managed_vis_overlays) if(!stat) SSvis_overlays.add_vis_overlay(src, icon, "smartfridge-light-mask", EMISSIVE_LAYER, EMISSIVE_PLANE, dir, alpha) - if(visible_contents) + if (visible_contents) switch(contents.len) if(0) icon_state = "[initial(icon_state)]" @@ -111,10 +112,10 @@ if(loaded) if(contents.len >= max_n_of_items) user.visible_message("[user] loads \the [src] with \the [O].", \ - "You fill \the [src] with \the [O].") + "You fill \the [src] with \the [O].") else user.visible_message("[user] loads \the [src] with \the [O].", \ - "You load \the [src] with \the [O].") + "You load \the [src] with \the [O].") if(O.contents.len > 0) to_chat(user, "Some items are refused.") if (visible_contents) @@ -172,6 +173,10 @@ var/listofitems = list() for (var/I in src) + // We do not vend our own components. + if(I in component_parts) + continue + var/atom/movable/O = I if (!QDELETED(O)) var/md5name = md5(O.name) // This needs to happen because of a bug in a TGUI component, https://github.com/ractivejs/ractive/issues/744 @@ -212,6 +217,8 @@ if(desired == 1 && Adjacent(usr) && !issilicon(usr)) for(var/obj/item/O in src) if(O.name == params["name"]) + if(O in component_parts) + CRASH("Attempted removal of [O] component_part from vending machine via vending interface.") dispense(O, usr) break if (visible_contents) @@ -222,6 +229,8 @@ if(desired <= 0) break if(O.name == params["name"]) + if(O in component_parts) + CRASH("Attempted removal of [O] component_part from vending machine via vending interface.") dispense(O, usr) desired-- if (visible_contents) @@ -242,13 +251,21 @@ idle_power_usage = 5 active_power_usage = 200 visible_contents = FALSE + base_build_path = /obj/machinery/smartfridge/drying_rack //should really be seeing this without admin fuckery. var/drying = FALSE /obj/machinery/smartfridge/drying_rack/Initialize() . = ..() - if(component_parts && component_parts.len) - component_parts.Cut() + + // Cache the old_parts first, we'll delete it after we've changed component_parts to a new list. + // This stops handle_atom_del being called on every part when not necessary. + var/list/old_parts = component_parts.Copy() + component_parts = null + circuit = null + + QDEL_LIST(old_parts) + RefreshParts() /obj/machinery/smartfridge/drying_rack/on_deconstruction() new /obj/item/stack/sheet/mineral/wood(drop_location(), 10) @@ -259,7 +276,9 @@ /obj/machinery/smartfridge/drying_rack/exchange_parts() /obj/machinery/smartfridge/drying_rack/spawn_frame() -/obj/machinery/smartfridge/drying_rack/default_deconstruction_crowbar(obj/item/crowbar/C, ignore_panel = 1) +/obj/machinery/smartfridge/drying_rack/default_deconstruction_crowbar(obj/item/C, ignore_panel = 1) + if(!C.tool_behaviour == TOOL_CROWBAR) + return ..() /obj/machinery/smartfridge/drying_rack/ui_data(mob/user) @@ -280,25 +299,18 @@ return TRUE return FALSE -// /obj/machinery/smartfridge/drying_rack/powered() do we have this? no. -// if(!anchored) -// return FALSE -// return ..() +/obj/machinery/smartfridge/drying_rack/powered() + if(!anchored) + return FALSE + return ..() /obj/machinery/smartfridge/drying_rack/power_change() - if(powered() && anchored) - stat &= ~NOPOWER - else - stat |= NOPOWER + . = ..() + if(!powered()) toggle_drying(TRUE) - update_icon() - - // . = ..() - // if(!powered()) - // toggle_drying(TRUE) /obj/machinery/smartfridge/drying_rack/load() //For updating the filled overlay - ..() + . = ..() update_icon() /obj/machinery/smartfridge/drying_rack/update_overlays() @@ -363,6 +375,7 @@ /obj/machinery/smartfridge/drinks name = "drink showcase" desc = "A refrigerated storage unit for tasty tasty alcohol." + base_build_path = /obj/machinery/smartfridge/drinks /obj/machinery/smartfridge/drinks/accept_check(obj/item/O) if(!istype(O, /obj/item/reagent_containers) || (O.item_flags & ABSTRACT) || !O.reagents || !O.reagents.reagent_list.len) @@ -375,6 +388,7 @@ // ---------------------------- /obj/machinery/smartfridge/food desc = "A refrigerated storage unit for food." + base_build_path = /obj/machinery/smartfridge/food /obj/machinery/smartfridge/food/accept_check(obj/item/O) if(istype(O, /obj/item/reagent_containers/food/snacks/)) @@ -387,6 +401,7 @@ /obj/machinery/smartfridge/extract name = "smart slime extract storage" desc = "A refrigerated storage unit for slime extracts." + base_build_path = /obj/machinery/smartfridge/extract /obj/machinery/smartfridge/extract/accept_check(obj/item/O) if(istype(O, /obj/item/slime_extract)) @@ -405,6 +420,7 @@ name = "smart organ storage" desc = "A refrigerated storage unit for organ storage." max_n_of_items = 20 //vastly lower to prevent processing too long + base_build_path = /obj/machinery/smartfridge/organ var/repair_rate = 0 /obj/machinery/smartfridge/organ/accept_check(obj/item/O) @@ -429,14 +445,14 @@ /obj/machinery/smartfridge/organ/RefreshParts() for(var/obj/item/stock_parts/matter_bin/B in component_parts) max_n_of_items = 20 * B.rating - repair_rate = max(0, STANDARD_ORGAN_HEALING * (B.rating - 1)) + repair_rate = max(0, STANDARD_ORGAN_HEALING * (B.rating - 1) * 0.5) -/obj/machinery/smartfridge/organ/process() +/obj/machinery/smartfridge/organ/process(delta_time) for(var/organ in contents) var/obj/item/organ/O = organ if(!istype(O)) return - O.applyOrganDamage(-repair_rate) + O.applyOrganDamage(-repair_rate * delta_time) /obj/machinery/smartfridge/organ/Exited(atom/movable/AM, atom/newLoc) . = ..() @@ -444,7 +460,9 @@ var/obj/item/organ/O = AM O.organ_flags &= ~ORGAN_FROZEN -/obj/machinery/smartfridge/organ/preloaded //cit specific?????? +//cit specific?????? +/obj/machinery/smartfridge/organ/preloaded + base_build_path = /obj/machinery/smartfridge/organ/preloaded initial_contents = list( /obj/item/reagent_containers/medspray/synthtissue = 1, /obj/item/reagent_containers/medspray/sterilizine = 1) @@ -461,6 +479,7 @@ /obj/machinery/smartfridge/chemistry name = "smart chemical storage" desc = "A refrigerated storage unit for medicine storage." + base_build_path = /obj/machinery/smartfridge/chemistry /obj/machinery/smartfridge/chemistry/accept_check(obj/item/O) var/static/list/chemfridge_typecache = typecacheof(list( @@ -502,6 +521,7 @@ /obj/machinery/smartfridge/chemistry/virology name = "smart virus storage" desc = "A refrigerated storage unit for volatile sample storage." + base_build_path = /obj/machinery/smartfridge/chemistry/virology /obj/machinery/smartfridge/chemistry/virology/preloaded initial_contents = list( @@ -523,6 +543,7 @@ icon_state = "disktoaster" pass_flags = PASSTABLE visible_contents = FALSE + base_build_path = /obj/machinery/smartfridge/disks /obj/machinery/smartfridge/disks/accept_check(obj/item/O) if(istype(O, /obj/item/disk/)) diff --git a/code/modules/food_and_drinks/recipes/tablecraft/recipes_donut.dm b/code/modules/food_and_drinks/recipes/tablecraft/recipes_donut.dm index c376ab4025..3d3b1ac0fd 100644 --- a/code/modules/food_and_drinks/recipes/tablecraft/recipes_donut.dm +++ b/code/modules/food_and_drinks/recipes/tablecraft/recipes_donut.dm @@ -21,7 +21,7 @@ ) result = /obj/item/reagent_containers/food/snacks/donut/chaos -datum/crafting_recipe/food/donut/meat +/datum/crafting_recipe/food/donut/meat time = 15 name = "Meat donut" reqs = list( diff --git a/code/modules/holiday/halloween/jacqueen.dm b/code/modules/holiday/halloween/jacqueen.dm index 90016954fc..11bd330112 100644 --- a/code/modules/holiday/halloween/jacqueen.dm +++ b/code/modules/holiday/halloween/jacqueen.dm @@ -50,16 +50,18 @@ var/cached_z /// I'm busy, don't move. var/busy = FALSE + var/static/blacklisted_items = typecacheof(list( - /obj/effect, - /obj/belly, - /obj/mafia_game_board, - /obj/docking_port, - /obj/shapeshift_holder, - /obj/screen)) + /obj/effect, + /obj/belly, + /obj/mafia_game_board, + /obj/docking_port, + /obj/shapeshift_holder, + /obj/screen + )) /mob/living/simple_animal/jacq/Initialize() - ..() + . = ..() //fuck you jacq, return a hint you shit cached_z = z poof() @@ -70,16 +72,14 @@ if((last_poof+3 MINUTES) < world.realtime) poof() -/mob/living/simple_animal/jacq/Destroy() //I.e invincible +/mob/living/simple_animal/jacq/death() //What is alive may never die visible_message("[src] cackles, \"You'll nae get rid a me that easily!\"") playsound(loc, 'sound/spookoween/ahaha.ogg', 100, 0.25) - var/mob/living/simple_animal/jacq/Jacq = new src.type(loc) - Jacq.progression = progression - if(ckey) //transfer over any ghost posessions - Jacq.key = key - ..() + fully_heal(FALSE) + health = 25 + poof() -/mob/living/simple_animal/jacq/death() //What is alive may never die +/mob/living/simple_animal/jacq/gib() visible_message("[src] cackles, \"You'll nae get rid a me that easily!\"") playsound(loc, 'sound/spookoween/ahaha.ogg', 100, 0.25) fully_heal(FALSE) @@ -158,23 +158,18 @@ return FALSE /mob/living/simple_animal/jacq/proc/gender_check(mob/living/carbon/C) - var/gender = "lamb" - if(C) - if(C.gender == MALE) - gender = "laddie" - if(C.gender == FEMALE) - gender = "lassie" - return gender + . = "lamb" + switch(C) + if(MALE) + . = "laddie" + if(FEMALE) + . = "lassie" //Ye wee bugger, gerrout of it. Ye've nae tae enjoy reading the code fer mae secrets like. /mob/living/simple_animal/jacq/proc/chit_chat(mob/living/carbon/C) //Very important var/gender = gender_check(C) - if(C) - if(C.gender == MALE) - gender = "laddie" - if(C.gender == FEMALE) - gender = "lassie" + // it physicaly cannot fail*. Why is there a fucking dupe if(!progression["[C.real_name]"] || !(progression["[C.real_name]"] & JACQ_HELLO)) visible_message("[src] smiles ominously at [C], \"Well halo there [gender]! Ah'm Jacqueline, tae great Pumpqueen, great tae meet ye.\"") diff --git a/code/modules/holodeck/computer.dm b/code/modules/holodeck/computer.dm index e5cd36cad6..e94cc17041 100644 --- a/code/modules/holodeck/computer.dm +++ b/code/modules/holodeck/computer.dm @@ -115,13 +115,20 @@ return FALSE var/valid = FALSE var/list/checked = program_cache.Copy() - if(obj_flags & EMAGGED) - checked |= emag_programs for(var/prog in checked) var/list/P = prog if(P["type"] == program_to_load) valid = TRUE break + if(obj_flags & EMAGGED) //split up into separate for loops instead of together so we can adminlog it + checked = emag_programs.Copy() + for(var/prog in checked) + var/list/P = prog + if(P["type"] == program_to_load) + valid = TRUE + log_game("[key_name(usr)] has loaded the restricted holodeck program [program_to_load]") + message_admins("[ADMIN_LOOKUPFLW(usr)] has loaded the restricted holodeck program [program_to_load]") + break if(!valid) return FALSE @@ -134,6 +141,14 @@ nerf(obj_flags & EMAGGED) obj_flags ^= EMAGGED say("Safeties restored. Restarting...") + if(obj_flags & EMAGGED) + to_chat(usr,"You vastly increase projector power and override the safety and security protocols.") + log_game("[key_name(usr)] has disabled safeties on the holodeck computer") + message_admins("[ADMIN_LOOKUPFLW(usr)] has disabled safeties on the holodeck computer") + else + to_chat(usr,"You restore the safeties to the holodeck.") + log_game("[key_name(usr)] has reenabled safeties on the holodeck computer") + message_admins("[ADMIN_LOOKUPFLW(usr)] has reenabled safeties on the holodeck computer") /obj/machinery/computer/holodeck/process() if(damaged && prob(10)) @@ -179,6 +194,7 @@ to_chat(user, "You vastly increase projector power and override the safety and security protocols.") say("Warning. Automatic shutoff and derezzing protocols have been corrupted. Please call Nanotrasen maintenance and do not use the simulator.") log_game("[key_name(user)] emagged the Holodeck Control Console") + message_admins("[ADMIN_LOOKUPFLW(user)] emagged the Holodeck Control Console.") nerf(!(obj_flags & EMAGGED)) /obj/machinery/computer/holodeck/emp_act(severity) diff --git a/code/modules/hydroponics/beekeeping/beebox.dm b/code/modules/hydroponics/beekeeping/beebox.dm index a058601c39..2857099d0f 100644 --- a/code/modules/hydroponics/beekeeping/beebox.dm +++ b/code/modules/hydroponics/beekeeping/beebox.dm @@ -155,7 +155,7 @@ to_chat(user, "There's no room for any more frames in the apiary!") return - if(istype(I, /obj/item/wrench)) + if(I.tool_behaviour == TOOL_WRENCH) if(default_unfasten_wrench(user, I, time = 20)) return diff --git a/code/modules/hydroponics/grown/cotton.dm b/code/modules/hydroponics/grown/cotton.dm index beff751479..daaf71c626 100644 --- a/code/modules/hydroponics/grown/cotton.dm +++ b/code/modules/hydroponics/grown/cotton.dm @@ -34,16 +34,14 @@ /obj/item/grown/cotton/attack_self(mob/user) user.show_message("You pull some [cotton_name] out of the [name]!", MSG_VISUAL) - var/seed_modifier = 0 - if(seed) - seed_modifier = round(seed.potency / 25) - var/obj/item/stack/cotton = new cotton_type(user.loc, 1 + seed_modifier) - var/old_cotton_amount = cotton.amount - for(var/obj/item/stack/ST in user.loc) - if(ST != cotton && istype(ST, cotton_type) && ST.amount < ST.max_amount) - ST.attackby(cotton, user) - if(cotton.amount > old_cotton_amount) - to_chat(user, "You add the newly-formed [cotton_name] to the stack. It now contains [cotton.amount] [cotton_name].") + var/cottonAmt = 1 + round(seed.potency / 25) // cotton inhand we're holding + for(var/obj/item/grown/cotton/ctn in user.loc) // cotton on the floor + if(ctn.type != type) + continue + cottonAmt += 1 + round(ctn.seed.potency / 25) + qdel(ctn) + new cotton_type(user.drop_location(), cottonAmt) + // above code stolen from grass qdel(src) //reinforced mutated variant diff --git a/code/modules/hydroponics/hydroponics.dm b/code/modules/hydroponics/hydroponics.dm index 8467d00124..aca727ad8d 100644 --- a/code/modules/hydroponics/hydroponics.dm +++ b/code/modules/hydroponics/hydroponics.dm @@ -92,6 +92,12 @@ return BULLET_ACT_HIT else if(istype(Proj , /obj/item/projectile/energy/florayield)) return myseed.bullet_act(Proj) + else if(istype(Proj , /obj/item/projectile/energy/florarevolution)) + if(myseed) + if(myseed.mutatelist.len > 0) + myseed.instability = (myseed.instability/2) + mutatespecie() + return BULLET_ACT_HIT else return ..() @@ -384,7 +390,6 @@ /obj/machinery/hydroponics/proc/hardmutate() mutate(4, 10, 2, 4, 50, 4, 10, 3) - /obj/machinery/hydroponics/proc/mutatespecie() // Mutagent produced a new plant! if(!myseed || dead) return @@ -523,12 +528,13 @@ else if(istype(O, /obj/item/seeds) && !istype(O, /obj/item/seeds/sample)) if(!myseed) if(istype(O, /obj/item/seeds/kudzu)) - investigate_log("had Kudzu planted in it by [key_name(user)] at [AREACOORD(src)]","kudzu") + investigate_log("had Kudzu planted in it by [key_name(user)] at [AREACOORD(src)]", INVESTIGATE_BOTANY) if(!user.transferItemToLoc(O, src)) return to_chat(user, "You plant [O].") dead = FALSE myseed = O + investigate_log("planting: [user] planted [O] with traits [english_list(myseed)] and reagents [english_list_assoc(myseed.reagents_add)] and potency [myseed.potency]", INVESTIGATE_BOTANY) TRAY_NAME_UPDATE age = 1 plant_health = myseed.endurance @@ -600,7 +606,34 @@ desc = initial(desc) weedlevel = 0 //Has a side effect of cleaning up those nasty weeds update_icon() - + else if(istype(O, /obj/item/gun/energy/floragun)) + var/obj/item/gun/energy/floragun/flowergun = O + if(flowergun.cell.charge < flowergun.cell.maxcharge) + to_chat(user, "[flowergun] must be fully charged to lock in a mutation!") + return + if(!myseed) + to_chat(user, "[src] is empty!") + return + if(myseed.endurance <= 20) + to_chat(user, "[myseed.plantname] isn't hardy enough to sequence its mutation!") + return + if(!myseed.mutatelist) + to_chat(user, "[myseed.plantname] has nothing else to mutate into!") + return + else + var/list/fresh_mut_list = list() + for(var/muties in myseed.mutatelist) + var/obj/item/seeds/another_mut = new muties + fresh_mut_list[another_mut.plantname] = muties + var/locked_mutation = (input(user, "Select a mutation to lock.", "Plant Mutation Locks") as null|anything in sortList(fresh_mut_list)) + if(!user.canUseTopic(src, BE_CLOSE) || !locked_mutation) + return + myseed.mutatelist = list(fresh_mut_list[locked_mutation]) + myseed.endurance = (myseed.endurance/2) + flowergun.cell.use(flowergun.cell.charge) + flowergun.update_icon() + to_chat(user, "[myseed.plantname]'s mutation was set to [locked_mutation], depleting [flowergun]'s cell!") + return else return ..() diff --git a/code/modules/hydroponics/seed_extractor.dm b/code/modules/hydroponics/seed_extractor.dm index 71701d9637..e0e15ac111 100644 --- a/code/modules/hydroponics/seed_extractor.dm +++ b/code/modules/hydroponics/seed_extractor.dm @@ -76,7 +76,7 @@ /obj/machinery/seed_extractor/examine(mob/user) . = ..() if(in_range(user, src) || isobserver(user)) - . += "The status display reads: Extracting [seed_multiplier] seed(s) per piece of produce.
    Machine can store up to [max_seeds]% seeds.
    " + . += "The status display reads: Extracting [seed_multiplier] seed(s) per piece of produce.
    Machine can store up to [max_seeds] seeds.
    " /obj/machinery/seed_extractor/attackby(obj/item/O, mob/user, params) diff --git a/code/modules/hydroponics/seeds.dm b/code/modules/hydroponics/seeds.dm index 2dc80cd8fc..577635cd1c 100644 --- a/code/modules/hydroponics/seeds.dm +++ b/code/modules/hydroponics/seeds.dm @@ -24,7 +24,7 @@ var/yield = 3 // Amount of growns created per harvest. If is -1, the plant/shroom/weed is never meant to be harvested. var/potency = 10 // The 'power' of a plant. Generally effects the amount of reagent in a plant, also used in other ways. var/growthstages = 6 // Amount of growth sprites the plant has. - var/instability = 5 //Chance that a plant will mutate in each stage of it's life. + var/instability = 5 //Chance that a plant will mutate in each stage of it's life. var/rarity = 0 // How rare the plant is. Used for giving points to cargo when shipping off to CentCom. var/list/mutatelist = list() // The type of plants that this plant can mutate into. var/list/genes = list() // Plant genes are stored here, see plant_genes.dm for more info. @@ -105,7 +105,7 @@ S.reagents_add = reagents_add.Copy() // Faster than grabbing the list from genes. return S -obj/item/seeds/proc/is_gene_forbidden(typepath) +/obj/item/seeds/proc/is_gene_forbidden(typepath) return (typepath in forbiddengenes) @@ -188,6 +188,8 @@ obj/item/seeds/proc/is_gene_forbidden(typepath) ///The Number of products produced by the plant, typically the yield. var/product_count = getYield() + parent.investigate_log("manual harvest by [key_name(user)] of [getYield()] of [src], with seed traits [english_list(genes)] and reagents_add [english_list_assoc(reagents_add)] and potency [potency].", INVESTIGATE_BOTANY) + while(t_amount < product_count) var/obj/item/reagent_containers/food/snacks/grown/t_prod if(instability >= 30 && (seed_flags & MUTATE_EARLY) && LAZYLEN(mutatelist) && prob(instability/3)) diff --git a/code/modules/instruments/songs/play_legacy.dm b/code/modules/instruments/songs/play_legacy.dm index eee9be3cc7..c4d86e2013 100644 --- a/code/modules/instruments/songs/play_legacy.dm +++ b/code/modules/instruments/songs/play_legacy.dm @@ -82,5 +82,5 @@ var/sound/music_played = sound(soundfile) for(var/i in hearing_mobs) var/mob/M = i - M.playsound_local(source, null, volume * using_instrument.volume_multiplier, falloff = 5, S = music_played) + M.playsound_local(source, null, volume * using_instrument.volume_multiplier, S = music_played) // Could do environment and echo later but not for now diff --git a/code/modules/integrated_electronics/core/assemblies.dm b/code/modules/integrated_electronics/core/assemblies.dm index 7c9f811c34..ac27b4a30e 100644 --- a/code/modules/integrated_electronics/core/assemblies.dm +++ b/code/modules/integrated_electronics/core/assemblies.dm @@ -452,7 +452,7 @@ for(var/obj/item/integrated_circuit/input/S in assembly_components) S.attackby_react(I,user,user.a_intent) return ..() - else if(istype(I, /obj/item/multitool) || istype(I, /obj/item/integrated_electronics/wirer) || istype(I, /obj/item/integrated_electronics/debugger)) + else if(I.tool_behaviour == TOOL_MULTITOOL || istype(I, /obj/item/integrated_electronics/wirer) || istype(I, /obj/item/integrated_electronics/debugger)) if(opened) interact(user) return TRUE @@ -578,6 +578,7 @@ return FALSE /obj/item/electronic_assembly/Moved(oldLoc, dir) + . = ..() for(var/I in assembly_components) var/obj/item/integrated_circuit/IC = I IC.ext_moved(oldLoc, dir) diff --git a/code/modules/integrated_electronics/core/integrated_circuit.dm b/code/modules/integrated_electronics/core/integrated_circuit.dm index f90b584d76..756706dcdf 100644 --- a/code/modules/integrated_electronics/core/integrated_circuit.dm +++ b/code/modules/integrated_electronics/core/integrated_circuit.dm @@ -252,7 +252,7 @@ a creative player the means to solve many problems. Circuits are held inside an var/update = TRUE var/update_to_assembly = FALSE - var/obj/held_item = usr.get_active_held_item() + var/obj/item/held_item = usr.get_active_held_item() if(href_list["rename"]) rename_component(usr) @@ -267,7 +267,7 @@ a creative player the means to solve many problems. Circuits are held inside an if(href_list["link"]) linked = locate(href_list["link"]) in pin.linked - if(istype(held_item, /obj/item/integrated_electronics) || istype(held_item, /obj/item/multitool)) + if(istype(held_item, /obj/item/integrated_electronics) || held_item.tool_behaviour == TOOL_MULTITOOL) pin.handle_wire(linked, held_item, href_list["act"], usr) else to_chat(usr, "You can't do a whole lot without the proper tools.") @@ -378,7 +378,7 @@ a creative player the means to solve many problems. Circuits are held inside an // Checks if the target object is reachable. Useful for various manipulators and manipulator-like objects. -/obj/item/integrated_circuit/proc/check_target(atom/target, exclude_contents = FALSE, exclude_components = FALSE, exclude_self = FALSE) +/obj/item/integrated_circuit/proc/check_target(atom/target, exclude_contents = FALSE, exclude_components = FALSE, exclude_self = FALSE, exclude_outside = FALSE) if(!target) return FALSE @@ -394,7 +394,7 @@ a creative player the means to solve many problems. Circuits are held inside an if(target == assembly.battery) return FALSE - if(target.Adjacent(acting_object) && isturf(target.loc)) + if(!exclude_outside && target.Adjacent(acting_object) && isturf(target.loc)) return TRUE if(!exclude_contents && (target in acting_object.GetAllContents())) diff --git a/code/modules/integrated_electronics/core/pins.dm b/code/modules/integrated_electronics/core/pins.dm index c1bbb900fa..e08d77007d 100644 --- a/code/modules/integrated_electronics/core/pins.dm +++ b/code/modules/integrated_electronics/core/pins.dm @@ -105,15 +105,14 @@ D [1]/ || push_data() /datum/integrated_io/proc/handle_wire(datum/integrated_io/linked_pin, obj/item/tool, action, mob/living/user) - if(istype(tool, /obj/item/multitool)) - var/obj/item/multitool/multitool = tool + if(tool.tool_behaviour == TOOL_MULTITOOL) switch(action) if("wire") - multitool.wire(src, user) + tool.wire(src, user) return TRUE if("unwire") if(linked_pin) - multitool.unwire(src, linked_pin, user) + tool.unwire(src, linked_pin, user) return TRUE if("data") ask_for_pin_data(user) diff --git a/code/modules/integrated_electronics/core/wirer.dm b/code/modules/integrated_electronics/core/wirer.dm index 95f46564cb..73dfa4e44a 100644 --- a/code/modules/integrated_electronics/core/wirer.dm +++ b/code/modules/integrated_electronics/core/wirer.dm @@ -12,13 +12,12 @@ icon_state = "wirer-wire" flags_1 = CONDUCT_1 w_class = WEIGHT_CLASS_SMALL - var/datum/integrated_io/selected_io = null var/mode = WIRE /obj/item/integrated_electronics/wirer/update_icon_state() icon_state = "wirer-[mode]" -/obj/item/integrated_electronics/wirer/proc/wire(var/datum/integrated_io/io, mob/user) +/obj/item/integrated_electronics/wirer/wire(var/datum/integrated_io/io, mob/user) if(!io.holder.assembly) to_chat(user, "\The [io.holder] needs to be secured inside an assembly first.") return diff --git a/code/modules/integrated_electronics/subtypes/atmospherics.dm b/code/modules/integrated_electronics/subtypes/atmospherics.dm index 26ac3080de..d449775bd1 100644 --- a/code/modules/integrated_electronics/subtypes/atmospherics.dm +++ b/code/modules/integrated_electronics/subtypes/atmospherics.dm @@ -292,7 +292,7 @@ activate_pin(2) // Required for making the connector port script work -obj/item/integrated_circuit/atmospherics/connector/portableConnectorReturnAir() +/obj/item/integrated_circuit/atmospherics/connector/portableConnectorReturnAir() return air_contents diff --git a/code/modules/integrated_electronics/subtypes/manipulation.dm b/code/modules/integrated_electronics/subtypes/manipulation.dm index eac16d2950..e32f8daa2d 100644 --- a/code/modules/integrated_electronics/subtypes/manipulation.dm +++ b/code/modules/integrated_electronics/subtypes/manipulation.dm @@ -186,6 +186,8 @@ AM.forceMove(src) /obj/item/integrated_circuit/manipulation/grabber/proc/drop(obj/item/AM, turf/T = drop_location()) + if(!check_target(AM, FALSE, TRUE, TRUE, TRUE)) + return var/atom/A = get_object() A.investigate_log("dropped ([AM]) from [src].", INVESTIGATE_CIRCUIT) AM.forceMove(T) diff --git a/code/modules/integrated_electronics/subtypes/weaponized.dm b/code/modules/integrated_electronics/subtypes/weaponized.dm index 96a732d08f..25fa7058fb 100644 --- a/code/modules/integrated_electronics/subtypes/weaponized.dm +++ b/code/modules/integrated_electronics/subtypes/weaponized.dm @@ -137,6 +137,10 @@ //Shooting Code: A.preparePixelProjectile(target, src) A.fire() + if(ismob(loc.loc)) + installed_gun.shoot_live_shot(loc.loc) + else + installed_gun.shoot_live_shot() //Shitcode, but we don't have much of a choice log_attack("[assembly] [REF(assembly)] has fired [installed_gun].") return A diff --git a/code/modules/jobs/job_exp.dm b/code/modules/jobs/job_exp.dm index 5ff791c5f3..d9b98eea0a 100644 --- a/code/modules/jobs/job_exp.dm +++ b/code/modules/jobs/job_exp.dm @@ -1,7 +1,6 @@ GLOBAL_LIST_EMPTY(exp_to_update) GLOBAL_PROTECT(exp_to_update) - // Procs /datum/job/proc/required_playtime_remaining(client/C) if(!C) @@ -57,6 +56,7 @@ GLOBAL_PROTECT(exp_to_update) amount += explist[job] return amount +// todo: port tgui exp /client/proc/get_exp_report() if(!CONFIG_GET(flag/use_exp_tracking)) return "Tracking is disabled in the server configuration file." @@ -121,12 +121,11 @@ GLOBAL_PROTECT(exp_to_update) return_text += "" return return_text - -/client/proc/get_exp_living() - if(!prefs.exp) - return "No data" +/client/proc/get_exp_living(pure_numeric = FALSE) + if(!prefs.exp || !prefs.exp[EXP_TYPE_LIVING]) + return pure_numeric ? 0 : "No data" var/exp_living = text2num(prefs.exp[EXP_TYPE_LIVING]) - return get_exp_format(exp_living) + return pure_numeric ? exp_living : get_exp_format(exp_living) /proc/get_exp_format(expnum) if(expnum > 60) @@ -148,7 +147,7 @@ GLOBAL_PROTECT(exp_to_update) set waitfor = FALSE var/list/old_minutes = GLOB.exp_to_update GLOB.exp_to_update = null - SSdbcore.MassInsert(format_table_name("role_time"), old_minutes, "ON DUPLICATE KEY UPDATE minutes = minutes + VALUES(minutes)") + SSdbcore.MassInsert(format_table_name("role_time"), old_minutes, duplicate_key = "ON DUPLICATE KEY UPDATE minutes = minutes + VALUES(minutes)") //resets a client's exp to what was in the db. /client/proc/set_exp_from_db() @@ -156,7 +155,10 @@ GLOBAL_PROTECT(exp_to_update) return -1 if(!SSdbcore.Connect()) return -1 - var/datum/DBQuery/exp_read = SSdbcore.NewQuery("SELECT job, minutes FROM [format_table_name("role_time")] WHERE ckey = '[sanitizeSQL(ckey)]'") + var/datum/db_query/exp_read = SSdbcore.NewQuery( + "SELECT job, minutes FROM [format_table_name("role_time")] WHERE ckey = :ckey", + list("ckey" = ckey) + ) if(!exp_read.Execute(async = TRUE)) qdel(exp_read) return -1 @@ -188,7 +190,10 @@ GLOBAL_PROTECT(exp_to_update) else prefs.db_flags |= newflag - var/datum/DBQuery/flag_update = SSdbcore.NewQuery("UPDATE [format_table_name("player")] SET flags = '[prefs.db_flags]' WHERE ckey='[sanitizeSQL(ckey)]'") + var/datum/db_query/flag_update = SSdbcore.NewQuery( + "UPDATE [format_table_name("player")] SET flags=:flags WHERE ckey=:ckey", + list("flags" = "[prefs.db_flags]", "ckey" = ckey) + ) if(!flag_update.Execute()) qdel(flag_update) @@ -256,8 +261,8 @@ GLOBAL_PROTECT(exp_to_update) CRASH("invalid job value [jtype]:[jvalue]") LAZYINITLIST(GLOB.exp_to_update) GLOB.exp_to_update.Add(list(list( - "job" = "'[sanitizeSQL(jtype)]'", - "ckey" = "'[sanitizeSQL(ckey)]'", + "job" = jtype, + "ckey" = ckey, "minutes" = jvalue))) prefs.exp[jtype] += jvalue addtimer(CALLBACK(SSblackbox,/datum/controller/subsystem/blackbox/proc/update_exp_db),20,TIMER_OVERRIDE|TIMER_UNIQUE) @@ -268,7 +273,10 @@ GLOBAL_PROTECT(exp_to_update) if(!SSdbcore.Connect()) return FALSE - var/datum/DBQuery/flags_read = SSdbcore.NewQuery("SELECT flags FROM [format_table_name("player")] WHERE ckey='[ckey]'") + var/datum/db_query/flags_read = SSdbcore.NewQuery( + "SELECT flags FROM [format_table_name("player")] WHERE ckey=:ckey", + list("ckey" = ckey) + ) if(!flags_read.Execute(async = TRUE)) qdel(flags_read) diff --git a/code/modules/jobs/job_report.dm b/code/modules/jobs/job_report.dm new file mode 100644 index 0000000000..88c7f7ad19 --- /dev/null +++ b/code/modules/jobs/job_report.dm @@ -0,0 +1,49 @@ +#define JOB_REPORT_MENU_FAIL_REASON_TRACKING_DISABLED 1 +#define JOB_REPORT_MENU_FAIL_REASON_NO_RECORDS 2 + +/datum/job_report_menu + var/client/owner + +/datum/job_report_menu/New(client/owner, mob/viewer) + src.owner = owner + ui_interact(viewer) + +/datum/job_report_menu/ui_state() + return GLOB.always_state + +/datum/job_report_menu/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if (!ui) + ui = new(user, src, "TrackedPlaytime") + ui.open() + +/datum/job_report_menu/ui_static_data() + if (!CONFIG_GET(flag/use_exp_tracking)) + return list("failReason" = JOB_REPORT_MENU_FAIL_REASON_TRACKING_DISABLED) + + var/list/play_records = owner.prefs.exp + if (!play_records.len) + owner.set_exp_from_db() + play_records = owner.prefs.exp + if (!play_records.len) + return list("failReason" = JOB_REPORT_MENU_FAIL_REASON_NO_RECORDS) + + var/list/data = list() + data["jobPlaytimes"] = list() + data["specialPlaytimes"] = list() + + for (var/job_name in SSjob.name_occupations) + var/playtime = play_records[job_name] ? text2num(play_records[job_name]) : 0 + data["jobPlaytimes"][job_name] = playtime + + for (var/special_name in GLOB.exp_specialmap[EXP_TYPE_SPECIAL]) + var/playtime = play_records[special_name] ? text2num(play_records[special_name]) : 0 + data["specialPlaytimes"][special_name] = playtime + + data["livingTime"] = play_records[EXP_TYPE_LIVING] + data["ghostTime"] = play_records[EXP_TYPE_GHOST] + + return data + +#undef JOB_REPORT_MENU_FAIL_REASON_TRACKING_DISABLED +#undef JOB_REPORT_MENU_FAIL_REASON_NO_RECORDS diff --git a/code/modules/jobs/job_types/_job.dm b/code/modules/jobs/job_types/_job.dm index 64ab4791d3..855e2ccbd7 100644 --- a/code/modules/jobs/job_types/_job.dm +++ b/code/modules/jobs/job_types/_job.dm @@ -187,7 +187,7 @@ /datum/job/proc/announce_head(var/mob/living/carbon/human/H, var/channels) //tells the given channel that the given mob is the new department head. See communications.dm for valid channels. if(H && GLOB.announcement_systems.len) //timer because these should come after the captain announcement - SSticker.OnRoundstart(CALLBACK(GLOBAL_PROC, .proc/addtimer, CALLBACK(pick(GLOB.announcement_systems), /obj/machinery/announcement_system/proc/announce, "NEWHEAD", H.real_name, H.job, channels), 1)) + SSticker.OnRoundstart(CALLBACK(GLOBAL_PROC, .proc/_addtimer, CALLBACK(pick(GLOB.announcement_systems), /obj/machinery/announcement_system/proc/announce, "NEWHEAD", H.real_name, H.job, channels), 1)) //If the configuration option is set to require players to be logged as old enough to play certain jobs, then this proc checks that they are, otherwise it just returns 1 /datum/job/proc/player_old_enough(client/C) diff --git a/code/modules/jobs/job_types/paramedic.dm b/code/modules/jobs/job_types/paramedic.dm index c8188cae8a..331bad9bfe 100644 --- a/code/modules/jobs/job_types/paramedic.dm +++ b/code/modules/jobs/job_types/paramedic.dm @@ -4,7 +4,7 @@ department_head = list("Chief Medical Officer") department_flag = MEDSCI faction = "Station" - total_positions = 3 + total_positions = 2 spawn_positions = 2 supervisors = "the chief medical officer" selection_color = "#74b5e0" diff --git a/code/modules/jobs/job_types/roboticist.dm b/code/modules/jobs/job_types/roboticist.dm index aa52b353df..b44d536cd2 100644 --- a/code/modules/jobs/job_types/roboticist.dm +++ b/code/modules/jobs/job_types/roboticist.dm @@ -32,7 +32,7 @@ l_pocket = /obj/item/pda/roboticist ears = /obj/item/radio/headset/headset_sci uniform = /obj/item/clothing/under/rank/rnd/roboticist - suit = /obj/item/clothing/suit/toggle/labcoat + suit = /obj/item/clothing/suit/toggle/labcoat/roboticist backpack = /obj/item/storage/backpack/science satchel = /obj/item/storage/backpack/satchel/tox diff --git a/code/modules/keybindings/keybind/carbon.dm b/code/modules/keybindings/keybind/carbon.dm index 46cb5cd0ac..50ec29ff5e 100644 --- a/code/modules/keybindings/keybind/carbon.dm +++ b/code/modules/keybindings/keybind/carbon.dm @@ -60,3 +60,14 @@ /datum/keybinding/carbon/select_harm_intent/down(client/user) user.mob?.a_intent_change(INTENT_HARM) return TRUE + +/datum/keybinding/carbon/give + hotkey_keys = list("CtrlG") + name = "Give_Item" + full_name = "Give item" + description = "Give the item you're currently holding" + +/datum/keybinding/carbon/give/down(client/user) + var/mob/living/carbon/C = user.mob + C.give() + return TRUE diff --git a/code/modules/language/signlanguage.dm b/code/modules/language/signlanguage.dm new file mode 100644 index 0000000000..97705d4a4c --- /dev/null +++ b/code/modules/language/signlanguage.dm @@ -0,0 +1,12 @@ +/datum/language/signlanguage + name = "Space Sign Language" + desc = "Those who cannot speak can learn this instead." + speech_verb = "signs" + whisper_verb = "gestures" + key = "9" + flags = TONGUELESS_SPEECH + + syllables = list(".") + + icon_state = "ssl" + default_priority = 90 diff --git a/code/modules/library/lib_items.dm b/code/modules/library/lib_items.dm index a4d88158e2..9d5dbe8f63 100644 --- a/code/modules/library/lib_items.dm +++ b/code/modules/library/lib_items.dm @@ -1,3 +1,7 @@ +#define BOOKCASE_UNANCHORED 0 +#define BOOKCASE_ANCHORED 1 +#define BOOKCASE_FINISHED 2 + /* Library Items * * Contains: @@ -17,69 +21,85 @@ desc = "A great place for storing knowledge." anchored = FALSE density = TRUE - opacity = 0 + opacity = FALSE resistance_flags = FLAMMABLE max_integrity = 200 armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 0) - var/state = 0 - var/list/allowed_books = list(/obj/item/book, /obj/item/spellbook, /obj/item/storage/book, /obj/item/gun/magic/wand/book) //Things allowed in the bookcase + var/state = BOOKCASE_UNANCHORED + /// When enabled, books_to_load number of random books will be generated for this bookcase when first interacted with. + var/load_random_books = FALSE + /// The category of books to pick from when populating random books. + var/random_category = null + /// How many random books to generate. + var/books_to_load = 0 /obj/structure/bookcase/examine(mob/user) . = ..() if(!anchored) . += "The bolts on the bottom are unsecured." - if(anchored) + else . += "It's secured in place with bolts." switch(state) - if(0) + if(BOOKCASE_UNANCHORED) . += "There's a small crack visible on the back panel." - if(1) + if(BOOKCASE_ANCHORED) . += "There's space inside for a wooden shelf." - if(2) + if(BOOKCASE_FINISHED) . += "There's a small crack visible on the shelf." /obj/structure/bookcase/Initialize(mapload) . = ..() if(!mapload) return - state = 2 - icon_state = "book-0" - anchored = TRUE + set_anchored(TRUE) + state = BOOKCASE_FINISHED for(var/obj/item/I in loc) - if(istype(I, /obj/item/book)) - I.forceMove(src) + if(!isbook(I)) + continue + I.forceMove(src) + update_icon() + +/obj/structure/bookcase/set_anchored(anchorvalue) + . = ..() + if(isnull(.)) + return + state = anchorvalue + if(!anchorvalue) //in case we were vareditted or uprooted by a hostile mob, ensure we drop all our books instead of having them disappear till we're rebuild. + var/atom/Tsec = drop_location() + for(var/obj/I in contents) + if(!isbook(I)) + continue + I.forceMove(Tsec) update_icon() /obj/structure/bookcase/attackby(obj/item/I, mob/user, params) switch(state) - if(0) - if(istype(I, /obj/item/wrench)) + if(BOOKCASE_UNANCHORED) + if(I.tool_behaviour == TOOL_WRENCH) if(I.use_tool(src, user, 20, volume=50)) to_chat(user, "You wrench the frame into place.") - anchored = TRUE - state = 1 - if(istype(I, /obj/item/crowbar)) + set_anchored(TRUE) + else if(I.tool_behaviour == TOOL_CROWBAR) if(I.use_tool(src, user, 20, volume=50)) to_chat(user, "You pry the frame apart.") deconstruct(TRUE) - if(1) + if(BOOKCASE_ANCHORED) if(istype(I, /obj/item/stack/sheet/mineral/wood)) var/obj/item/stack/sheet/mineral/wood/W = I if(W.get_amount() >= 2) W.use(2) to_chat(user, "You add a shelf.") - state = 2 - icon_state = "book-0" - if(istype(I, /obj/item/wrench)) + state = BOOKCASE_FINISHED + update_icon() + else if(I.tool_behaviour == TOOL_WRENCH) I.play_tool_sound(src, 100) to_chat(user, "You unwrench the frame.") - anchored = FALSE - state = 0 + set_anchored(FALSE) - if(2) + if(BOOKCASE_FINISHED) var/datum/component/storage/STR = I.GetComponent(/datum/component/storage) - if(is_type_in_list(I, allowed_books)) + if(isbook(I)) if(!user.transferItemToLoc(I, src)) return update_icon() @@ -100,26 +120,29 @@ return else name = "bookcase ([sanitize(newname)])" - else if(istype(I, /obj/item/crowbar)) + else if(I.tool_behaviour == TOOL_CROWBAR) if(contents.len) to_chat(user, "You need to remove the books first!") else I.play_tool_sound(src, 100) to_chat(user, "You pry the shelf out.") new /obj/item/stack/sheet/mineral/wood(drop_location(), 2) - state = 1 - icon_state = "bookempty" + state = BOOKCASE_ANCHORED + update_icon() else return ..() -/obj/structure/bookcase/on_attack_hand(mob/living/user, act_intent = user.a_intent, unarmed_attack_flags) - . = ..() - if(. || !istype(user)) + +/obj/structure/bookcase/on_attack_hand(mob/living/user) + if(!istype(user)) return + if(load_random_books) + create_random_books(books_to_load, src, FALSE, random_category) + load_random_books = FALSE if(contents.len) - var/obj/item/book/choice = input("Which book would you like to remove from the shelf?") as null|obj in contents + var/obj/item/book/choice = input(user, "Which book would you like to remove from the shelf?") as null|obj in sortNames(contents.Copy()) if(choice) - if(!CHECK_MOBILITY(user, MOBILITY_USE) || !in_range(loc, user)) + if(!(user.mobility_flags & MOBILITY_USE) || user.stat != CONSCIOUS || !in_range(loc, user)) return if(ishuman(user)) if(!user.get_active_held_item()) @@ -128,36 +151,25 @@ choice.forceMove(drop_location()) update_icon() -/obj/structure/bookcase/attack_ghost(mob/dead/observer/user) - . = ..() - if(!length(contents)) - to_chat(user, "It's empty!") - return - var/obj/item/book/choice = input("Which book would you like to read?") as null|obj in contents - if(choice) - if(!istype(choice)) //spellbook, cult tome, or the one weird bible storage - to_chat(user,"A mysterious force is keeping you from reading that.") - return - choice.attack_ghost(user) /obj/structure/bookcase/deconstruct(disassembled = TRUE) - new /obj/item/stack/sheet/mineral/wood(loc, 4) - for(var/obj/item/book/B in contents) - B.forceMove(get_turf(src)) - qdel(src) + var/atom/Tsec = drop_location() + new /obj/item/stack/sheet/mineral/wood(Tsec, 4) + for(var/obj/item/I in contents) + if(!isbook(I)) + continue + I.forceMove(Tsec) + return ..() /obj/structure/bookcase/update_icon_state() - icon_state = "book-[min(length(contents), 5)]" - - -/obj/structure/bookcase/manuals/medical - name = "medical manuals bookcase" - -/obj/structure/bookcase/manuals/medical/Initialize() - . = ..() - new /obj/item/book/manual/wiki/medical_cloning(src) - update_icon() + if(state == BOOKCASE_UNANCHORED || state == BOOKCASE_ANCHORED) + icon_state = "bookempty" + return + var/amount = contents.len + if(load_random_books) + amount += books_to_load + icon_state = "book-[clamp(amount, 0, 5)]" /obj/structure/bookcase/manuals/engineering @@ -198,34 +210,27 @@ var/dat //Actual page content var/due_date = 0 //Game time in 1/10th seconds var/author //Who wrote the thing, can be changed by pen or PC. It is not automatically assigned - var/unique = 0 //0 - Normal book, 1 - Should not be treated as normal book, unable to be copied, unable to be modified + var/unique = FALSE //false - Normal book, true - Should not be treated as normal book, unable to be copied, unable to be modified var/title //The real name of the book. var/window_size = null // Specific window size for the book, i.e: "1920x1080", Size x Width + /obj/item/book/attack_self(mob/user) - if(is_blind(user)) - to_chat(user, "As you are trying to read, you suddenly feel very stupid!") - return - if(ismonkey(user)) - to_chat(user, "You skim through the book but can't comprehend any of it.") + if(!user.can_read(src)) return if(dat) - show_to(user) - user.visible_message("[user] opens a book titled \"[title]\" and begins reading intently.") + user << browse("Penned by [author].
    " + "[dat]", "window=book[window_size != null ? ";size=[window_size]" : ""]") + user.visible_message("[user] opens a book titled \"[title]\" and begins reading intently.") + // SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "book_nerd", /datum/mood_event/book_nerd) + onclose(user, "book") else to_chat(user, "This book is completely blank!") -/obj/item/book/attack_ghost(mob/dead/observer/O) - . = ..() - show_to(O) - -/obj/item/book/proc/show_to(mob/user) - user << browse("Penned by [author].
    " + "[dat]", "window=book[window_size != null ? ";size=[window_size]" : ""]") /obj/item/book/attackby(obj/item/I, mob/user, params) if(istype(I, /obj/item/pen)) - if(is_blind(user)) - to_chat(user, " As you are trying to write on the book, you suddenly feel very stupid!") + if(user.is_blind()) + to_chat(user, "As you are trying to write on the book, you suddenly feel very stupid!") return if(unique) to_chat(user, "These pages don't seem to take the ink well! Looks like you can't modify it.") @@ -243,10 +248,10 @@ if(!user.canUseTopic(src, BE_CLOSE, literate)) return if (length(newtitle) > 20) - to_chat(user, "That title won't fit on the cover!") + to_chat(user, "That title won't fit on the cover!") return if(!newtitle) - to_chat(user, "That title is invalid.") + to_chat(user, "That title is invalid.") return else name = newtitle @@ -256,7 +261,7 @@ if(!user.canUseTopic(src, BE_CLOSE, literate)) return if(!content) - to_chat(user, "The content is invalid.") + to_chat(user, "The content is invalid.") return else dat += content @@ -265,7 +270,7 @@ if(!user.canUseTopic(src, BE_CLOSE, literate)) return if(!newauthor) - to_chat(user, "The name is invalid.") + to_chat(user, "The name is invalid.") return else author = newauthor @@ -275,34 +280,34 @@ else if(istype(I, /obj/item/barcodescanner)) var/obj/item/barcodescanner/scanner = I if(!scanner.computer) - to_chat(user, "[I]'s screen flashes: 'No associated computer found!'") + to_chat(user, "[I]'s screen flashes: 'No associated computer found!'") else switch(scanner.mode) if(0) scanner.book = src - to_chat(user, "[I]'s screen flashes: 'Book stored in buffer.'") + to_chat(user, "[I]'s screen flashes: 'Book stored in buffer.'") if(1) scanner.book = src scanner.computer.buffer_book = name - to_chat(user, "[I]'s screen flashes: 'Book stored in buffer. Book title stored in associated computer buffer.'") + to_chat(user, "[I]'s screen flashes: 'Book stored in buffer. Book title stored in associated computer buffer.'") if(2) scanner.book = src for(var/datum/borrowbook/b in scanner.computer.checkouts) if(b.bookname == name) scanner.computer.checkouts.Remove(b) - to_chat(user, "[I]'s screen flashes: 'Book stored in buffer. Book has been checked in.'") + to_chat(user, "[I]'s screen flashes: 'Book stored in buffer. Book has been checked in.'") return - to_chat(user, "[I]'s screen flashes: 'Book stored in buffer. No active check-out record found for current title.'") + to_chat(user, "[I]'s screen flashes: 'Book stored in buffer. No active check-out record found for current title.'") if(3) scanner.book = src for(var/obj/item/book in scanner.computer.inventory) if(book == src) - to_chat(user, "[I]'s screen flashes: 'Book stored in buffer. Title already present in inventory, aborting to avoid duplicate entry.'") + to_chat(user, "[I]'s screen flashes: 'Book stored in buffer. Title already present in inventory, aborting to avoid duplicate entry.'") return scanner.computer.inventory.Add(src) - to_chat(user, "[I]'s screen flashes: 'Book stored in buffer. Title added to general inventory.'") + to_chat(user, "[I]'s screen flashes: 'Book stored in buffer. Title added to general inventory.'") - else if(istype(I, /obj/item/kitchen/knife) || istype(I, /obj/item/wirecutters)) + else if(istype(I, /obj/item/kitchen/knife) || I.tool_behaviour == TOOL_WIRECUTTER) to_chat(user, "You begin to carve out [title]...") if(do_after(user, 30, target = src)) to_chat(user, "You carve out the pages from [title]! You didn't want to read it anyway.") @@ -361,3 +366,8 @@ else to_chat(user, "No associated computer found. Only local scans will function properly.") to_chat(user, "\n") + + +#undef BOOKCASE_UNANCHORED +#undef BOOKCASE_ANCHORED +#undef BOOKCASE_FINISHED diff --git a/code/modules/library/lib_machines.dm b/code/modules/library/lib_machines.dm index 3953f5e28c..1125d15bca 100644 --- a/code/modules/library/lib_machines.dm +++ b/code/modules/library/lib_machines.dm @@ -25,12 +25,13 @@ var/title var/category = "Any" var/author - var/SQLquery - clockwork = TRUE //it'd look weird + var/search_page = 0 + COOLDOWN_DECLARE(library_visitor_topic_cooldown) + clockwork = TRUE /obj/machinery/computer/libraryconsole/ui_interact(mob/user) . = ..() - var/dat = "" // + var/list/dat = list() // switch(screenstate) if(0) dat += "

    Search Settings


    " @@ -43,13 +44,43 @@ dat += "ERROR: Unable to contact External Archive. Please contact your system administrator for assistance.
    " else if(QDELETED(user)) return - else if(!SQLquery) - dat += "ERROR: Malformed search request. Please contact your system administrator for assistance.
    " else dat += "" dat += "" - - var/datum/DBQuery/query_library_list_books = SSdbcore.NewQuery(SQLquery) + var/bookcount = 0 + var/booksperpage = 20 + var/datum/db_query/query_library_count_books = SSdbcore.NewQuery({" + SELECT COUNT(id) FROM [format_table_name("library")] + WHERE isnull(deleted) + AND author LIKE CONCAT('%',:author,'%') + AND title LIKE CONCAT('%',:title,'%') + AND (:category = 'Any' OR category = :category) + "}, list("author" = author, "title" = title, "category" = category)) + if(!query_library_count_books.warn_execute()) + qdel(query_library_count_books) + return + if(query_library_count_books.NextRow()) + bookcount = text2num(query_library_count_books.item[1]) + qdel(query_library_count_books) + if(bookcount > booksperpage) + dat += "Page: " + var/pagecount = 1 + var/list/pagelist = list() + while(bookcount > 0) + pagelist += "[pagecount == search_page + 1 ? "\[[pagecount]\]" : "\[[pagecount]\]"]" + bookcount -= booksperpage + pagecount++ + dat += pagelist.Join(" | ") + search_page = text2num(search_page) + var/datum/db_query/query_library_list_books = SSdbcore.NewQuery({" + SELECT author, title, category, id + FROM [format_table_name("library")] + WHERE isnull(deleted) + AND author LIKE CONCAT('%',:author,'%') + AND title LIKE CONCAT('%',:title,'%') + AND (:category = 'Any' OR category = :category) + LIMIT :skip, :take + "}, list("author" = author, "title" = title, "category" = category, "skip" = booksperpage * search_page, "take" = booksperpage)) if(!query_library_list_books.Execute()) dat += "ERROR: Unable to retrieve book listings. Please contact your system administrator for assistance.
    " else @@ -65,12 +96,15 @@ dat += "
    AUTHORTITLECATEGORYSS13BN

    " dat += "\[Go Back\]
    " var/datum/browser/popup = new(user, "publiclibrary", name, 600, 400) - popup.set_content(dat) + popup.set_content(jointext(dat, "")) popup.open() /obj/machinery/computer/libraryconsole/Topic(href, href_list) + if(!COOLDOWN_FINISHED(src, library_visitor_topic_cooldown)) + return + COOLDOWN_START(src, library_visitor_topic_cooldown, 1 SECONDS) . = ..() - if(..()) + if(.) usr << browse(null, "window=publiclibrary") onclose(usr, "publiclibrary") return @@ -81,29 +115,24 @@ title = sanitize(newtitle) else title = null - title = sanitizeSQL(title) if(href_list["setcategory"]) var/newcategory = input("Choose a category to search for:") in list("Any", "Fiction", "Non-Fiction", "Adult", "Reference", "Religion") if(newcategory) category = sanitize(newcategory) else category = "Any" - category = sanitizeSQL(category) if(href_list["setauthor"]) var/newauthor = input("Enter an author to search for:") as text|null if(newauthor) author = sanitize(newauthor) else author = null - author = sanitizeSQL(author) if(href_list["search"]) - SQLquery = "SELECT author, title, category, id FROM [format_table_name("library")] WHERE isnull(deleted) AND " - if(category == "Any") - SQLquery += "author LIKE '%[author]%' AND title LIKE '%[title]%'" - else - SQLquery += "author LIKE '%[author]%' AND title LIKE '%[title]%' AND category='[category]'" screenstate = 1 + if(href_list["bookpagecount"]) + search_page = text2num(href_list["bookpagecount"]) + if(href_list["back"]) screenstate = 0 @@ -120,44 +149,12 @@ var/getdate var/duedate -/* - * Cachedbook datum - */ -/datum/cachedbook // Datum used to cache the SQL DB books locally in order to achieve a performance gain. - var/id - var/title - var/author - var/category - -GLOBAL_LIST(cachedbooks) // List of our cached book datums - - -/proc/load_library_db_to_cache() - if(GLOB.cachedbooks) - return - if(!SSdbcore.Connect()) - return - GLOB.cachedbooks = list() - var/datum/DBQuery/query_library_cache = SSdbcore.NewQuery("SELECT id, author, title, category FROM [format_table_name("library")] WHERE isnull(deleted)") - if(!query_library_cache.Execute()) - qdel(query_library_cache) - return - while(query_library_cache.NextRow()) - var/datum/cachedbook/newbook = new() - newbook.id = query_library_cache.item[1] - newbook.author = query_library_cache.item[2] - newbook.title = query_library_cache.item[3] - newbook.category = query_library_cache.item[4] - GLOB.cachedbooks += newbook - qdel(query_library_cache) - - - #define PRINTER_COOLDOWN 60 /* * Library Computer - * After 860 days, it's finally a buildable computer. + * After 860 days, it's finally a buildable computer.* + * * i cannot change maps because you are a buch of fucks who ignore map changes */ // TODO: Make this an actual /obj/machinery/computer that can be crafted from circuit boards and such // It is August 22nd, 2012... This TODO has already been here for months.. I wonder how long it'll last before someone does something about it. @@ -165,11 +162,15 @@ GLOBAL_LIST(cachedbooks) // List of our cached book datums /obj/machinery/computer/libraryconsole/bookmanagement name = "book inventory management console" desc = "Librarian's command station." - screenstate = 0 // 0 - Main Menu, 1 - Inventory, 2 - Checked Out, 3 - Check Out a Book verb_say = "beeps" verb_ask = "beeps" verb_exclaim = "beeps" pass_flags = PASSTABLE + + circuit = /obj/item/circuitboard/computer/libraryconsole + + // var/screenstate = 0 // 0 - Main Menu, 1 - Inventory, 2 - Checked Out, 3 - Check Out a Book + var/arcanecheckout = 0 var/buffer_book var/buffer_mob @@ -178,25 +179,9 @@ GLOBAL_LIST(cachedbooks) // List of our cached book datums var/list/inventory = list() var/checkoutperiod = 5 // In minutes var/obj/machinery/libraryscanner/scanner // Book scanner that will be used when uploading books to the Archive - var/list/libcomp_menu var/page = 1 //current page of the external archives - var/cooldown = 0 - -/obj/machinery/computer/libraryconsole/bookmanagement/proc/build_library_menu() - if(libcomp_menu) - return - load_library_db_to_cache() - if(!GLOB.cachedbooks) - return - libcomp_menu = list("") - - for(var/i in 1 to GLOB.cachedbooks.len) - var/datum/cachedbook/C = GLOB.cachedbooks[i] - var/page = round(i/250)+1 - if (libcomp_menu.len < page) - libcomp_menu.len = page - libcomp_menu[page] = "" - libcomp_menu[page] += "[C.author][C.title][C.category]\[Order\]\n" + var/printer_cooldown = 0 + COOLDOWN_DECLARE(library_console_topic_cooldown) /obj/machinery/computer/libraryconsole/bookmanagement/Initialize() . = ..() @@ -258,17 +243,37 @@ GLOBAL_LIST(cachedbooks) // List of our cached book datums dat += "(Return to main menu)
    " if(4) dat += "

    External Archive

    " - build_library_menu() - - if(!GLOB.cachedbooks) + if(!SSdbcore.Connect()) dat += "ERROR: Unable to contact External Archive. Please contact your system administrator for assistance." else + var/booksperpage = 50 + var/pagecount + var/datum/db_query/query_library_count_books = SSdbcore.NewQuery("SELECT COUNT(id) FROM [format_table_name("library")] WHERE isnull(deleted)") + if(!query_library_count_books.Execute()) + qdel(query_library_count_books) + return + if(query_library_count_books.NextRow()) + pagecount = CEILING(text2num(query_library_count_books.item[1]) / booksperpage, 1) + qdel(query_library_count_books) + var/list/booklist = list() + var/datum/db_query/query_library_get_books = SSdbcore.NewQuery({" + SELECT id, author, title, category + FROM [format_table_name("library")] + WHERE isnull(deleted) + LIMIT :skip, :take + "}, list("skip" = booksperpage * (page - 1), "take" = booksperpage)) + if(!query_library_get_books.Execute()) + qdel(query_library_get_books) + return + while(query_library_get_books.NextRow()) + booklist += "[query_library_get_books.item[2]][query_library_get_books.item[3]][query_library_get_books.item[4]]\[Order\]\n" dat += "(Order book by SS13BN)

    " dat += "" dat += "" - dat += libcomp_menu[clamp(page,1,libcomp_menu.len)] - dat += "" + dat += jointext(booklist, "") + dat += "" dat += "
    AUTHORTITLECATEGORY
    <<<< >>>>
    <<<< >>>>
    " + qdel(query_library_get_books) dat += "
    (Return to main menu)
    " if(5) dat += "

    Upload a New Title

    " @@ -321,33 +326,27 @@ GLOBAL_LIST(cachedbooks) // List of our cached book datums return null /obj/machinery/computer/libraryconsole/bookmanagement/proc/print_forbidden_lore(mob/user) - var/spook = pick("blood", "brass") - var/turf/T = get_turf(src) - if(spook == "blood") - new /obj/item/melee/cultblade/dagger(T) - else - new /obj/item/clockwork/slab(T) - - to_chat(user, "Your sanity barely endures the seconds spent in the vault's browsing window. The only thing to remind you of this when you stop browsing is a [spook == "blood" ? "sinister dagger" : "strange metal tablet"] sitting on the desk. You don't even remember where it came from...") - user.visible_message("[user] stares at the blank screen for a few moments, [user.p_their()] expression frozen in fear. When [user.p_they()] finally awaken[user.p_s()] from it, [user.p_they()] look[user.p_s()] a lot older.", 2) + new /obj/item/melee/cultblade/dagger(get_turf(src)) + to_chat(user, "Your sanity barely endures the seconds spent in the vault's browsing window. The only thing to remind you of this when you stop browsing is a sinister dagger sitting on the desk. You don't even remember where it came from...") + user.visible_message("[user] stares at the blank screen for a few moments, [user.p_their()] expression frozen in fear. When [user.p_they()] finally awaken[user.p_s()] from it, [user.p_they()] look[user.p_s()] a lot older.", 2) /obj/machinery/computer/libraryconsole/bookmanagement/attackby(obj/item/W, mob/user, params) if(istype(W, /obj/item/barcodescanner)) var/obj/item/barcodescanner/scanner = W scanner.computer = src - to_chat(user, "[scanner]'s associated machine has been set to [src].") - audible_message("[src] lets out a low, short blip.") + to_chat(user, "[scanner]'s associated machine has been set to [src].") + audible_message("[src] lets out a low, short blip.") else return ..() /obj/machinery/computer/libraryconsole/bookmanagement/emag_act(mob/user) - . = ..() - if(!density || obj_flags & EMAGGED) - return - obj_flags |= EMAGGED - return TRUE + if(density && !(obj_flags & EMAGGED)) + obj_flags |= EMAGGED /obj/machinery/computer/libraryconsole/bookmanagement/Topic(href, href_list) + if(!COOLDOWN_FINISHED(src, library_console_topic_cooldown)) + return + COOLDOWN_START(src, library_console_topic_cooldown, 1 SECONDS) if(..()) usr << browse(null, "window=library") onclose(usr, "library") @@ -385,7 +384,7 @@ GLOBAL_LIST(cachedbooks) // List of our cached book datums if(checkoutperiod < 1) checkoutperiod = 1 if(href_list["editbook"]) - buffer_book = stripped_input(usr, "Enter the book's title:") + buffer_book = stripped_input(usr, "Enter the book's title:", max_length = 45) if(href_list["editmob"]) buffer_mob = stripped_input(usr, "Enter the recipient's name:", max_length = MAX_NAME_LEN) if(href_list["checkout"]) @@ -404,7 +403,7 @@ GLOBAL_LIST(cachedbooks) // List of our cached book datums if(b && istype(b)) inventory.Remove(b) if(href_list["setauthor"]) - var/newauthor = stripped_input(usr, "Enter the author's name: ") + var/newauthor = stripped_input(usr, "Enter the author's name: ", max_length = 45) if(newauthor) scanner.cache.author = newauthor if(href_list["setcategory"]) @@ -419,14 +418,11 @@ GLOBAL_LIST(cachedbooks) // List of our cached book datums if (!SSdbcore.Connect()) alert("Connection to Archive has been severed. Aborting.") else - - var/sqltitle = sanitizeSQL(scanner.cache.name) - var/sqlauthor = sanitizeSQL(scanner.cache.author) - var/sqlcontent = sanitizeSQL(scanner.cache.dat) - var/sqlcategory = sanitizeSQL(upload_category) - var/sqlckey = sanitizeSQL(usr.ckey) var/msg = "[key_name(usr)] has uploaded the book titled [scanner.cache.name], [length(scanner.cache.dat)] signs" - var/datum/DBQuery/query_library_upload = SSdbcore.NewQuery("INSERT INTO [format_table_name("library")] (author, title, content, category, ckey, datetime, round_id_created) VALUES ('[sqlauthor]', '[sqltitle]', '[sqlcontent]', '[sqlcategory]', '[sqlckey]', Now(), '[GLOB.round_id]')") + var/datum/db_query/query_library_upload = SSdbcore.NewQuery({" + INSERT INTO [format_table_name("library")] (author, title, content, category, ckey, datetime, round_id_created) + VALUES (:author, :title, :content, :category, :ckey, Now(), :round_id) + "}, list("title" = scanner.cache.name, "author" = scanner.cache.author, "content" = scanner.cache.dat, "category" = upload_category, "ckey" = usr.ckey, "round_id" = GLOB.round_id)) if(!query_library_upload.Execute()) qdel(query_library_upload) alert("Database error encountered uploading to Archive") @@ -448,7 +444,7 @@ GLOBAL_LIST(cachedbooks) // List of our cached book datums GLOB.news_network.SubmitArticle(scanner.cache.dat, "[scanner.cache.name]", "Nanotrasen Book Club", null) alert("Upload complete. Your uploaded title is now available on station newscasters.") if(href_list["orderbyid"]) - if(cooldown > world.time) + if(printer_cooldown > world.time) say("Printer unavailable. Please allow a short time before attempting to print.") else var/orderid = input("Enter your order:") as num|null @@ -457,14 +453,17 @@ GLOBAL_LIST(cachedbooks) // List of our cached book datums href_list["targetid"] = num2text(orderid) if(href_list["targetid"]) - var/sqlid = sanitizeSQL(href_list["targetid"]) + var/id = href_list["targetid"] if (!SSdbcore.Connect()) alert("Connection to Archive has been severed. Aborting.") - if(cooldown > world.time) + if(printer_cooldown > world.time) say("Printer unavailable. Please allow a short time before attempting to print.") else - cooldown = world.time + PRINTER_COOLDOWN - var/datum/DBQuery/query_library_print = SSdbcore.NewQuery("SELECT * FROM [format_table_name("library")] WHERE id=[sqlid] AND isnull(deleted)") + printer_cooldown = world.time + PRINTER_COOLDOWN + var/datum/db_query/query_library_print = SSdbcore.NewQuery( + "SELECT * FROM [format_table_name("library")] WHERE id=:id AND isnull(deleted)", + list("id" = id) + ) if(!query_library_print.Execute()) qdel(query_library_print) say("PRINTER ERROR! Failed to print document (0x0000000F)") @@ -480,24 +479,24 @@ GLOBAL_LIST(cachedbooks) // List of our cached book datums B.author = author B.dat = content B.icon_state = "book[rand(1,8)]" - visible_message("[src]'s printer hums as it produces a completely bound book. How did it do that?") + visible_message("[src]'s printer hums as it produces a completely bound book. How did it do that?") break qdel(query_library_print) if(href_list["printbible"]) - if(cooldown < world.time) + if(printer_cooldown < world.time) var/obj/item/storage/book/bible/B = new /obj/item/storage/book/bible(src.loc) if(GLOB.bible_icon_state && GLOB.bible_item_state) B.icon_state = GLOB.bible_icon_state B.item_state = GLOB.bible_item_state B.name = GLOB.bible_name B.deity_name = GLOB.deity - cooldown = world.time + PRINTER_COOLDOWN + printer_cooldown = world.time + PRINTER_COOLDOWN else say("Printer currently unavailable, please wait a moment.") if(href_list["printposter"]) - if(cooldown < world.time) + if(printer_cooldown < world.time) new /obj/item/poster/random_official(src.loc) - cooldown = world.time + PRINTER_COOLDOWN + printer_cooldown = world.time + PRINTER_COOLDOWN else say("Printer currently unavailable, please wait a moment.") add_fingerprint(usr) @@ -521,7 +520,10 @@ GLOBAL_LIST(cachedbooks) // List of our cached book datums else return ..() -/obj/machinery/libraryscanner/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags) +/obj/machinery/libraryscanner/attack_hand(mob/user) + . = ..() + if(.) + return usr.set_machine(src) var/dat = "" // if(cache) @@ -584,14 +586,14 @@ GLOBAL_LIST(cachedbooks) // List of our cached book datums return if(!user.transferItemToLoc(P, src)) return - user.visible_message("[user] loads some paper into [src].", "You load some paper into [src].") - audible_message("[src] begins to hum as it warms up its printing drums.") + user.visible_message("[user] loads some paper into [src].", "You load some paper into [src].") + audible_message("[src] begins to hum as it warms up its printing drums.") busy = TRUE sleep(rand(200,400)) busy = FALSE if(P) if(!stat) - visible_message("[src] whirs as it prints and binds a new book.") + visible_message("[src] whirs as it prints and binds a new book.") var/obj/item/book/B = new(src.loc) B.dat = P.info B.name = "Print Job #" + "[rand(100, 999)]" diff --git a/code/modules/library/random_books.dm b/code/modules/library/random_books.dm index accd477387..d60609147a 100644 --- a/code/modules/library/random_books.dm +++ b/code/modules/library/random_books.dm @@ -10,78 +10,83 @@ /obj/item/book/random icon_state = "random_book" - var/amount = 1 - var/category = null + /// The category of books to pick from when creating this book. + var/random_category = null + /// If this book has already been 'generated' yet. + var/random_loaded = FALSE -/obj/item/book/random/Initialize() - ..() - create_random_books(amount, src.loc, TRUE, category) - return INITIALIZE_HINT_QDEL +/obj/item/book/random/Initialize(mapload) + . = ..() + icon_state = "book[rand(1,8)]" -/obj/item/book/random/triple - amount = 3 +/obj/item/book/random/attack_self() + if(!random_loaded) + create_random_books(1, loc, TRUE, random_category, src) + random_loaded = TRUE + return ..() /obj/structure/bookcase/random - var/category = null - var/book_count = 2 + load_random_books = TRUE + books_to_load = 2 icon_state = "random_bookcase" - anchored = TRUE - state = 2 /obj/structure/bookcase/random/Initialize(mapload) . = ..() - if(!book_count || !isnum(book_count)) - update_icon() - return - book_count += pick(-1,-1,0,1,1) - create_random_books(book_count, src, FALSE, category) + if(books_to_load && isnum(books_to_load)) + books_to_load += pick(-1,-1,0,1,1) update_icon() -/proc/create_random_books(amount = 2, location, fail_loud = FALSE, category = null) +/proc/create_random_books(amount, location, fail_loud = FALSE, category = null, obj/item/book/existing_book) . = list() if(!isnum(amount) || amount<1) return if (!SSdbcore.Connect()) - if(fail_loud || prob(5)) - var/obj/item/paper/P = new(location) - P.info = "There once was a book from Nantucket
    But the database failed us, so f*$! it.
    I tried to be good to you
    Now this is an I.O.U
    If you're feeling entitled, well, stuff it!

    ~" - P.update_icon() + if(existing_book && (fail_loud || prob(5))) + existing_book.author = "???" + existing_book.title = "Strange book" + existing_book.name = "Strange book" + existing_book.dat = "There once was a book from Nantucket
    But the database failed us, so f*$! it.
    I tried to be good to you
    Now this is an I.O.U
    If you're feeling entitled, well, stuff it!

    ~" return if(prob(25)) category = null - var/c = category? " AND category='[sanitizeSQL(category)]'" :"" - var/datum/DBQuery/query_get_random_books = SSdbcore.NewQuery("SELECT * FROM [format_table_name("library")] WHERE isnull(deleted)[c] GROUP BY title ORDER BY rand() LIMIT [amount];") // isdeleted copyright (c) not me + var/datum/db_query/query_get_random_books = SSdbcore.NewQuery({" + SELECT author, title, content + FROM [format_table_name("library")] + WHERE isnull(deleted) AND (:category IS NULL OR category = :category) + ORDER BY rand() LIMIT :limit + "}, list("category" = category, "limit" = amount)) if(query_get_random_books.Execute()) while(query_get_random_books.NextRow()) - var/obj/item/book/B = new(location) - . += B - B.author = query_get_random_books.item[2] - B.title = query_get_random_books.item[3] - B.dat = query_get_random_books.item[4] + var/obj/item/book/B + B = existing_book ? existing_book : new(location) + B.author = query_get_random_books.item[1] + B.title = query_get_random_books.item[2] + B.dat = query_get_random_books.item[3] B.name = "Book: [B.title]" - B.icon_state= "book[rand(1,8)]" + if(!existing_book) + B.icon_state= "book[rand(1,8)]" qdel(query_get_random_books) /obj/structure/bookcase/random/fiction name = "bookcase (Fiction)" - category = "Fiction" + random_category = "Fiction" /obj/structure/bookcase/random/nonfiction name = "bookcase (Non-Fiction)" - category = "Non-fiction" + random_category = "Non-fiction" /obj/structure/bookcase/random/religion name = "bookcase (Religion)" - category = "Religion" + random_category = "Religion" /obj/structure/bookcase/random/adult name = "bookcase (Adult)" - category = "Adult" + random_category = "Adult" /obj/structure/bookcase/random/reference name = "bookcase (Reference)" - category = "Reference" + random_category = "Reference" var/ref_book_prob = 20 /obj/structure/bookcase/random/reference/Initialize(mapload) . = ..() - while(book_count > 0 && prob(ref_book_prob)) - book_count-- + while(books_to_load > 0 && prob(ref_book_prob)) + books_to_load-- new /obj/item/book/manual/random(src) diff --git a/code/modules/mafia/controller.dm b/code/modules/mafia/controller.dm index cd8c382f30..54dcaebeec 100644 --- a/code/modules/mafia/controller.dm +++ b/code/modules/mafia/controller.dm @@ -1,21 +1,21 @@ /** - * The mafia controller handles the mafia minigame in progress. - * It is first created when the first ghost signs up to play. - */ + * The mafia controller handles the mafia minigame in progress. + * It is first created when the first ghost signs up to play. + */ /datum/mafia_controller ///list of observers that should get game updates. var/list/spectators = list() ///all roles in the game, dead or alive. check their game status if you only want living or dead. var/list/all_roles = list() - ///exists to speed up role retrieval, it's a dict. player_role_lookup[player ckey] will give you the role they play + ///exists to speed up role retrieval, it's a dict. `player_role_lookup[player ckey]` will give you the role they play var/list/player_role_lookup = list() ///what part of the game you're playing in. day phases, night phases, judgement phases, etc. var/phase = MAFIA_PHASE_SETUP ///how long the game has gone on for, changes with every sunrise. day one, night one, day two, etc. var/turn = 0 - ///for debugging and testing a full game, or adminbuse. If this is not null, it will use this as a setup. clears when game is over + ///for debugging and testing a full game, or adminbuse. If this is not empty, it will use this as a setup. clears when game is over var/list/custom_setup = list() ///first day has no voting, and thus is shorter var/first_day_phase_period = 20 SECONDS @@ -82,20 +82,20 @@ qdel(map_deleter) /** - * Triggers at beginning of the game when there is a confirmed list of valid, ready players. - * Creates a 100% ready game that has NOT started (no players in bodies) - * Followed by start game - * - * Does the following: - * * Picks map, and loads it - * * Grabs landmarks if it is the first time it's loading - * * Sets up the role list - * * Puts players in each role randomly - * Arguments: - * * setup_list: list of all the datum setups (fancy list of roles) that would work for the game - * * ready_players: list of filtered, sane players (so not playing or disconnected) for the game to put into roles - */ -/datum/mafia_controller/proc/prepare_game(setup_list, ready_players) + * Triggers at beginning of the game when there is a confirmed list of valid, ready players. + * Creates a 100% ready game that has NOT started (no players in bodies) + * Followed by start game + * + * Does the following: + * * Picks map, and loads it + * * Grabs landmarks if it is the first time it's loading + * * Sets up the role list + * * Puts players in each role randomly + * Arguments: + * * setup_list: list of all the datum setups (fancy list of roles) that would work for the game + * * ready_players: list of filtered, sane players (so not playing or disconnected) for the game to put into roles + */ +/datum/mafia_controller/proc/prepare_game(setup_list,ready_players) var/list/possible_maps = subtypesof(/datum/map_template/mafia) var/turf/spawn_area = get_turf(locate(/obj/effect/landmark/mafia_game_area) in GLOB.landmarks_list) @@ -166,23 +166,23 @@ to_chat(M, "[link] MAFIA: [msg] [team_suffix]") /** - * The game by this point is now all set up, and so we can put people in their bodies and start the first phase. - * - * Does the following: - * * Creates bodies for all of the roles with the first proc - * * Starts the first day manually (so no timer) with the second proc - */ + * The game by this point is now all set up, and so we can put people in their bodies and start the first phase. + * + * Does the following: + * * Creates bodies for all of the roles with the first proc + * * Starts the first day manually (so no timer) with the second proc + */ /datum/mafia_controller/proc/start_game() create_bodies() start_day() /** - * How every day starts. - * - * What players do in this phase: - * * If day one, just a small starting period to see who is in the game and check role, leading to the night phase. - * * Otherwise, it's a longer period used to discuss events that happened during the night, leading to the voting phase. - */ + * How every day starts. + * + * What players do in this phase: + * * If day one, just a small starting period to see who is in the game and check role, leading to the night phase. + * * Otherwise, it's a longer period used to discuss events that happened during the night, leading to the voting phase. + */ /datum/mafia_controller/proc/start_day() turn += 1 phase = MAFIA_PHASE_DAY @@ -198,12 +198,12 @@ SStgui.update_uis(src) /** - * Players have finished the discussion period, and now must put up someone to the chopping block. - * - * What players do in this phase: - * * Vote on which player to put up for lynching, leading to the judgement phase. - * * If no votes are case, the judgement phase is skipped, leading to the night phase. - */ + * Players have finished the discussion period, and now must put up someone to the chopping block. + * + * What players do in this phase: + * * Vote on which player to put up for lynching, leading to the judgement phase. + * * If no votes are case, the judgement phase is skipped, leading to the night phase. + */ /datum/mafia_controller/proc/start_voting_phase() phase = MAFIA_PHASE_VOTING next_phase_timer = addtimer(CALLBACK(src, .proc/check_trial, TRUE),voting_phase_period,TIMER_STOPPABLE) //be verbose! @@ -211,21 +211,21 @@ SStgui.update_uis(src) /** - * Players have voted someone up, and now the person must defend themselves while the town votes innocent or guilty. - * - * What players do in this phase: - * * Vote innocent or guilty, if they are not on trial. - * * Defend themselves and wait for judgement, if they are. - * * Leads to the lynch phase. - * Arguments: - * * verbose: boolean, announces whether there were votes or not. after judgement it goes back here with no voting period to end the day. - */ + * Players have voted someone up, and now the person must defend themselves while the town votes innocent or guilty. + * + * What players do in this phase: + * * Vote innocent or guilty, if they are not on trial. + * * Defend themselves and wait for judgement, if they are. + * * Leads to the lynch phase. + * Arguments: + * * verbose: boolean, announces whether there were votes or not. after judgement it goes back here with no voting period to end the day. + */ /datum/mafia_controller/proc/check_trial(verbose = TRUE) var/datum/mafia_role/loser = get_vote_winner("Day")//, majority_of_town = TRUE) - // var/loser_votes = get_vote_count(loser,"Day") + var/loser_votes = get_vote_count(loser,"Day") if(loser) - // if(loser_votes > 12) - // loser.body.client?.give_award(/datum/award/achievement/mafia/universally_hated, loser.body) + if(loser_votes > 12) + award_role(/datum/award/achievement/mafia/universally_hated, loser) send_message("[loser.body.real_name] wins the day vote, Listen to their defense and vote \"INNOCENT\" or \"GUILTY\"!") //refresh the lists judgement_abstain_votes = list() @@ -248,12 +248,12 @@ SStgui.update_uis(src) /** - * Players have voted innocent or guilty on the person on trial, and that person is now killed or returned home. - * - * What players do in this phase: - * * r/watchpeopledie - * * If the accused is killed, their true role is revealed to the rest of the players. - */ + * Players have voted innocent or guilty on the person on trial, and that person is now killed or returned home. + * + * What players do in this phase: + * * r/watchpeopledie + * * If the accused is killed, their true role is revealed to the rest of the players. + */ /datum/mafia_controller/proc/lynch() for(var/i in judgement_innocent_votes) var/datum/mafia_role/role = i @@ -276,25 +276,25 @@ next_phase_timer = addtimer(CALLBACK(src, .proc/check_trial, FALSE),judgement_lynch_period,TIMER_STOPPABLE)// small pause to see the guy dead, no verbosity since we already did this /** - * Teenie helper proc to move players back to their home. - * Used in the above, but also used in the debug button "send all players home" - * Arguments: - * * role: mafia role that is getting sent back to the game. - */ + * Teenie helper proc to move players back to their home. + * Used in the above, but also used in the debug button "send all players home" + * Arguments: + * * role: mafia role that is getting sent back to the game. + */ /datum/mafia_controller/proc/send_home(datum/mafia_role/role) role.body.forceMove(get_turf(role.assigned_landmark)) /** - * Checks to see if a faction (or solo antagonist) has won. - * - * Calculates in this order: - * * counts up town, mafia, and solo - * * solos can count as town members for the purposes of mafia winning - * * sends the amount of living people to the solo antagonists, and see if they won OR block the victory of the teams - * * checks if solos won from above, then if town, then if mafia - * * starts the end of the game if a faction won - * * returns TRUE if someone won the game, halting other procs from continuing in the case of a victory - */ + * Checks to see if a faction (or solo antagonist) has won. + * + * Calculates in this order: + * * counts up town, mafia, and solo + * * solos can count as town members for the purposes of mafia winning + * * sends the amount of living people to the solo antagonists, and see if they won OR block the victory of the teams + * * checks if solos won from above, then if town, then if mafia + * * starts the end of the game if a faction won + * * returns TRUE if someone won the game, halting other procs from continuing in the case of a victory + */ /datum/mafia_controller/proc/check_victory() //needed for achievements var/list/total_town = list() @@ -336,8 +336,7 @@ var/solo_end = FALSE for(var/datum/mafia_role/winner in total_victors) send_message("!! [uppertext(winner.name)] VICTORY !!") - // var/client/winner_client = GLOB.directory[winner.player_key] - // winner_client?.give_award(winner.winner_award, winner.body) + award_role(winner.winner_award, winner) solo_end = TRUE if(solo_end) start_the_end() @@ -345,28 +344,39 @@ if(blocked_victory) return FALSE if(alive_mafia == 0) - // for(var/datum/mafia_role/townie in total_town) - // var/client/townie_client = GLOB.directory[townie.player_key] - // townie_client?.give_award(townie.winner_award, townie.body) + for(var/datum/mafia_role/townie in total_town) + award_role(townie.winner_award, townie) start_the_end("!! TOWN VICTORY !!") return TRUE else if(alive_mafia >= alive_town) //guess could change if town nightkill is added start_the_end("!! MAFIA VICTORY !!") - // for(var/datum/mafia_role/changeling in total_mafia) - // var/client/changeling_client = GLOB.directory[changeling.player_key] - // changeling_client?.give_award(changeling.winner_award, changeling.body) + for(var/datum/mafia_role/changeling in total_mafia) + award_role(changeling.winner_award, changeling) return TRUE /** - * The end of the game is in two procs, because we want a bit of time for players to see eachothers roles. - * Because of how check_victory works, the game is halted in other places by this point. - * - * What players do in this phase: - * * See everyone's role postgame - * * See who won the game - * Arguments: - * * message: string, if non-null it sends it to all players. used to announce team victories while solos are handled in check victory - */ + * Lets the game award roles with all their checks and sanity, prevents achievements given out for debug games + * + * Arguments: + * * award: path of the award + * * role: mafia_role datum to reward. + */ +/datum/mafia_controller/proc/award_role(award, datum/mafia_role/rewarded) + if(custom_setup.len) + return + var/client/role_client = GLOB.directory[rewarded.player_key] + role_client?.give_award(award, rewarded.body) + +/** + * The end of the game is in two procs, because we want a bit of time for players to see eachothers roles. + * Because of how check_victory works, the game is halted in other places by this point. + * + * What players do in this phase: + * * See everyone's role postgame + * * See who won the game + * Arguments: + * * message: string, if non-null it sends it to all players. used to announce team victories while solos are handled in check victory + */ /datum/mafia_controller/proc/start_the_end(message) SEND_SIGNAL(src,COMSIG_MAFIA_GAME_END) if(message) @@ -377,8 +387,8 @@ next_phase_timer = addtimer(CALLBACK(src,.proc/end_game),victory_lap_period,TIMER_STOPPABLE) /** - * Cleans up the game, resetting variables back to the beginning and removing the map with the generator. - */ + * Cleans up the game, resetting variables back to the beginning and removing the map with the generator. + */ /datum/mafia_controller/proc/end_game() map_deleter.generate() //remove the map, it will be loaded at the start of the next one QDEL_LIST(all_roles) @@ -392,17 +402,17 @@ phase = MAFIA_PHASE_SETUP /** - * After the voting and judgement phases, the game goes to night shutting the windows and beginning night with a proc. - */ + * After the voting and judgement phases, the game goes to night shutting the windows and beginning night with a proc. + */ /datum/mafia_controller/proc/lockdown() toggle_night_curtains(close=TRUE) start_night() /** - * Shuts poddoors attached to mafia. - * Arguments: - * * close: boolean, the state you want the curtains in. - */ + * Shuts poddoors attached to mafia. + * Arguments: + * * close: boolean, the state you want the curtains in. + */ /datum/mafia_controller/proc/toggle_night_curtains(close) for(var/obj/machinery/door/poddoor/D in GLOB.machines) //I really dislike pathing of these if(D.id != "mafia") //so as to not trigger shutters on station, lol @@ -413,12 +423,12 @@ INVOKE_ASYNC(D, /obj/machinery/door/poddoor.proc/open) /** - * The actual start of night for players. Mostly info is given at the start of the night as the end of the night is when votes and actions are submitted and tried. - * - * What players do in this phase: - * * Mafia are told to begin voting on who to kill - * * Powers that are picked during the day announce themselves right now - */ + * The actual start of night for players. Mostly info is given at the start of the night as the end of the night is when votes and actions are submitted and tried. + * + * What players do in this phase: + * * Mafia are told to begin voting on who to kill + * * Powers that are picked during the day announce themselves right now + */ /datum/mafia_controller/proc/start_night() phase = MAFIA_PHASE_NIGHT send_message("Night [turn] started! Lockdown will end in 45 seconds.") @@ -427,16 +437,16 @@ SStgui.update_uis(src) /** - * The end of the night, and a series of signals for the order of events on a night. - * - * Order of events, and what they mean: - * * Start of resolve (NIGHT_START) is for activating night abilities that MUST go first - * * Action phase (NIGHT_ACTION_PHASE) is for non-lethal day abilities - * * Mafia then tallies votes and kills the highest voted person (note: one random voter visits that person for the purposes of roleblocking) - * * Killing phase (NIGHT_KILL_PHASE) is for lethal night abilities - * * End of resolve (NIGHT_END) is for cleaning up abilities that went off and i guess doing some that must go last - * * Finally opens the curtains and calls the start of day phase, completing the cycle until check victory returns TRUE - */ + * The end of the night, and a series of signals for the order of events on a night. + * + * Order of events, and what they mean: + * * Start of resolve (NIGHT_START) is for activating night abilities that MUST go first + * * Action phase (NIGHT_ACTION_PHASE) is for non-lethal day abilities + * * Mafia then tallies votes and kills the highest voted person (note: one random voter visits that person for the purposes of roleblocking) + * * Killing phase (NIGHT_KILL_PHASE) is for lethal night abilities + * * End of resolve (NIGHT_END) is for cleaning up abilities that went off and i guess doing some that must go last + * * Finally opens the curtains and calls the start of day phase, completing the cycle until check victory returns TRUE + */ /datum/mafia_controller/proc/resolve_night() SEND_SIGNAL(src,COMSIG_MAFIA_NIGHT_START) SEND_SIGNAL(src,COMSIG_MAFIA_NIGHT_ACTION_PHASE) @@ -457,15 +467,15 @@ SStgui.update_uis(src) /** - * Proc that goes off when players vote for something with their mafia panel. - * - * If teams, it hides the tally overlay and only sends the vote messages to the team that is voting - * Arguments: - * * voter: the mafia role that is trying to vote for... - * * target: the mafia role that is getting voted for - * * vote_type: type of vote submitted (is this the day vote? is this the mafia night vote?) - * * teams: see mafia team defines for what to put in, makes the messages only send to a specific team (so mafia night votes only sending messages to mafia at night) - */ + * Proc that goes off when players vote for something with their mafia panel. + * + * If teams, it hides the tally overlay and only sends the vote messages to the team that is voting + * Arguments: + * * voter: the mafia role that is trying to vote for... + * * target: the mafia role that is getting voted for + * * vote_type: type of vote submitted (is this the day vote? is this the mafia night vote?) + * * teams: see mafia team defines for what to put in, makes the messages only send to a specific team (so mafia night votes only sending messages to mafia at night) + */ /datum/mafia_controller/proc/vote_for(datum/mafia_role/voter,datum/mafia_role/target,vote_type, teams) if(!votes[vote_type]) votes[vote_type] = list() @@ -485,8 +495,8 @@ old.body.update_icon() /** - * Clears out the votes of a certain type (day votes, mafia kill votes) while leaving others untouched - */ + * Clears out the votes of a certain type (day votes, mafia kill votes) while leaving others untouched + */ /datum/mafia_controller/proc/reset_votes(vote_type) var/list/bodies_to_update = list() for(var/vote in votes[vote_type]) @@ -497,11 +507,11 @@ M.update_icon() /** - * Returns how many people voted for the role, in whatever vote (day vote, night kill vote) - * Arguments: - * * role: the mafia role the proc tries to get the amount of votes for - * * vote_type: the vote type (getting how many day votes were for the role, or mafia night votes for the role) - */ + * Returns how many people voted for the role, in whatever vote (day vote, night kill vote) + * Arguments: + * * role: the mafia role the proc tries to get the amount of votes for + * * vote_type: the vote type (getting how many day votes were for the role, or mafia night votes for the role) + */ /datum/mafia_controller/proc/get_vote_count(role,vote_type) . = 0 for(var/v in votes[vote_type]) @@ -510,11 +520,11 @@ . += votee.vote_power /** - * Returns whichever role got the most votes, in whatever vote (day vote, night kill vote) - * returns null if no votes - * Arguments: - * * vote_type: the vote type (getting the role that got the most day votes, or the role that got the most mafia votes) - */ + * Returns whichever role got the most votes, in whatever vote (day vote, night kill vote) + * returns null if no votes + * Arguments: + * * vote_type: the vote type (getting the role that got the most day votes, or the role that got the most mafia votes) + */ /datum/mafia_controller/proc/get_vote_winner(vote_type) var/list/tally = list() for(var/votee in votes[vote_type]) @@ -526,21 +536,23 @@ return length(tally) ? tally[1] : null /** - * Returns a random person who voted for whatever vote (day vote, night kill vote) - * Arguments: - * * vote_type: vote type (getting a random day voter, or mafia night voter) - */ + * Returns a random person who voted for whatever vote (day vote, night kill vote) + * Arguments: + * * vote_type: vote type (getting a random day voter, or mafia night voter) + */ /datum/mafia_controller/proc/get_random_voter(vote_type) if(length(votes[vote_type])) return pick(votes[vote_type]) /** - * Adds mutable appearances to people who get publicly voted on (so not night votes) showing how many people are picking them - * Arguments: - * * source: the body of the role getting the overlays - * * overlay_list: signal var passing the overlay list of the mob - */ + * Adds mutable appearances to people who get publicly voted on (so not night votes) showing how many people are picking them + * Arguments: + * * source: the body of the role getting the overlays + * * overlay_list: signal var passing the overlay list of the mob + */ /datum/mafia_controller/proc/display_votes(atom/source, list/overlay_list) + SIGNAL_HANDLER + if(phase != MAFIA_PHASE_VOTING) return var/v = get_vote_count(player_role_lookup[source],"Day") @@ -548,14 +560,14 @@ overlay_list += MA /** - * Called when the game is setting up, AFTER map is loaded but BEFORE the phase timers start. Creates and places each role's body and gives the correct player key - * - * Notably: - * * Toggles godmode so the mafia players cannot kill themselves - * * Adds signals for voting overlays, see display_votes proc - * * gives mafia panel - * * sends the greeting text (goals, role name, etc) - */ + * Called when the game is setting up, AFTER map is loaded but BEFORE the phase timers start. Creates and places each role's body and gives the correct player key + * + * Notably: + * * Toggles godmode so the mafia players cannot kill themselves + * * Adds signals for voting overlays, see display_votes proc + * * gives mafia panel + * * sends the greeting text (goals, role name, etc) + */ /datum/mafia_controller/proc/create_bodies() for(var/datum/mafia_role/role in all_roles) var/mob/living/carbon/human/H = new(get_turf(role.assigned_landmark)) @@ -716,13 +728,14 @@ if(GLOB.mafia_signup[C.ckey]) GLOB.mafia_signup -= C.ckey to_chat(usr, "You unregister from Mafia.") - return + return TRUE else GLOB.mafia_signup[C.ckey] = C to_chat(usr, "You sign up for Mafia.") if(phase == MAFIA_PHASE_SETUP) check_signups() try_autostart() + return TRUE if("mf_spectate") if(C.ckey in spectators) to_chat(usr, "You will no longer get messages from the game.") @@ -730,6 +743,7 @@ else to_chat(usr, "You will now get messages from the game.") spectators += C.ckey + return TRUE if(user_role.game_status == MAFIA_DEAD) return //User actions (just living) @@ -800,13 +814,13 @@ . += L[key] /** - * Returns a semirandom setup, with... - * Town, Two invest roles, one protect role, sometimes a misc role, and the rest assistants for town. - * Mafia, 2 normal mafia and one special. - * Neutral, two disruption roles, sometimes one is a killing. - * - * See _defines.dm in the mafia folder for a rundown on what these groups of roles include. - */ + * Returns a semirandom setup, with... + * Town, Two invest roles, one protect role, sometimes a misc role, and the rest assistants for town. + * Mafia, 2 normal mafia and one special. + * Neutral, two disruption roles, sometimes one is a killing. + * + * See _defines.dm in the mafia folder for a rundown on what these groups of roles include. + */ /datum/mafia_controller/proc/generate_random_setup() var/invests_left = 2 var/protects_left = 1 @@ -845,8 +859,8 @@ return random_setup /** - * Helper proc that adds a random role of a type to a setup. if it doesn't exist in the setup, it adds the path to the list and otherwise bumps the path in the list up one - */ + * Helper proc that adds a random role of a type to a setup. if it doesn't exist in the setup, it adds the path to the list and otherwise bumps the path in the list up one + */ /datum/mafia_controller/proc/add_setup_role(setup_list, wanted_role_type) var/list/role_type_paths = list() for(var/path in typesof(/datum/mafia_role)) @@ -868,17 +882,17 @@ setup_list[mafia_path] = 1 /** - * Called when enough players have signed up to fill a setup. DOESN'T NECESSARILY MEAN THE GAME WILL START. - * - * Checks for a custom setup, if so gets the required players from that and if not it sets the player requirement to required_player(max_player) and generates one IF basic setup starts a game. - * Checks if everyone signed up is an observer, and is still connected. If people aren't, they're removed from the list. - * If there aren't enough players post sanity, it aborts. otherwise, it selects enough people for the game and starts preparing the game for real. - */ + * Called when enough players have signed up to fill a setup. DOESN'T NECESSARILY MEAN THE GAME WILL START. + * + * Checks for a custom setup, if so gets the required players from that and if not it sets the player requirement to MAFIA_MAX_PLAYER_COUNT and generates one IF basic setup starts a game. + * Checks if everyone signed up is an observer, and is still connected. If people aren't, they're removed from the list. + * If there aren't enough players post sanity, it aborts. otherwise, it selects enough people for the game and starts preparing the game for real. + */ /datum/mafia_controller/proc/basic_setup() var/req_players var/list/setup = custom_setup if(!setup.len) - req_players = required_player //max_player + req_players = max_player //MAFIA_MAX_PLAYER_COUNT else req_players = assoc_value_sum(setup) @@ -918,10 +932,10 @@ start_game() /** - * Called when someone signs up, and sees if there are enough people in the signup list to begin. - * - * Only checks if everyone is actually valid to start (still connected and an observer) if there are enough players (basic_setup) - */ + * Called when someone signs up, and sees if there are enough people in the signup list to begin. + * + * Only checks if everyone is actually valid to start (still connected and an observer) if there are enough players (basic_setup) + */ /datum/mafia_controller/proc/try_autostart() if(phase != MAFIA_PHASE_SETUP) // || !(GLOB.ghost_role_flags & GHOSTROLE_MINIGAME)) return @@ -929,10 +943,10 @@ basic_setup() /** - * Filters inactive player into a different list until they reconnect, and removes players who are no longer ghosts. - * - * If a disconnected player gets a non-ghost mob and reconnects, they will be first put back into mafia_signup then filtered by that. - */ + * Filters inactive player into a different list until they reconnect, and removes players who are no longer ghosts. + * + * If a disconnected player gets a non-ghost mob and reconnects, they will be first put back into mafia_signup then filtered by that. + */ /datum/mafia_controller/proc/check_signups() for(var/bad_key in GLOB.mafia_bad_signup) if(GLOB.directory[bad_key])//they have reconnected if we can search their key and get a client @@ -962,8 +976,8 @@ parent.ui_interact(owner) /** - * Creates the global datum for playing mafia games, destroys the last if that's required and returns the new. - */ + * Creates the global datum for playing mafia games, destroys the last if that's required and returns the new. + */ /proc/create_mafia_game() if(GLOB.mafia_game) QDEL_NULL(GLOB.mafia_game) diff --git a/code/modules/mafia/roles.dm b/code/modules/mafia/roles.dm index 2461a93976..a210c4994f 100644 --- a/code/modules/mafia/roles.dm +++ b/code/modules/mafia/roles.dm @@ -19,7 +19,7 @@ var/list/actions = list() var/list/targeted_actions = list() //what the role gets when it wins a game - // var/winner_award = /datum/award/achievement/mafia/assistant + var/winner_award = /datum/award/achievement/mafia/assistant //so mafia have to also kill them to have a majority var/solo_counts_as_town = FALSE //(don't set this for town) @@ -124,7 +124,7 @@ desc = "You can investigate a single person each night to learn their team." revealed_outfit = /datum/outfit/mafia/detective role_type = TOWN_INVEST - // winner_award = /datum/award/achievement/mafia/detective + winner_award = /datum/award/achievement/mafia/detective hud_icon = "huddetective" revealed_icon = "detective" @@ -151,6 +151,8 @@ current_investigation = target /datum/mafia_role/detective/proc/investigate(datum/mafia_controller/game) + SIGNAL_HANDLER + var/datum/mafia_role/target = current_investigation if(target) if(target.detect_immune) @@ -178,7 +180,7 @@ desc = "You can visit someone ONCE PER GAME to reveal their true role in the morning!" revealed_outfit = /datum/outfit/mafia/psychologist role_type = TOWN_INVEST - // winner_award = /datum/award/achievement/mafia/psychologist + winner_award = /datum/award/achievement/mafia/psychologist hud_icon = "hudpsychologist" revealed_icon = "psychologist" @@ -202,6 +204,8 @@ current_target = target /datum/mafia_role/psychologist/proc/therapy_reveal(datum/mafia_controller/game) + SIGNAL_HANDLER + if(SEND_SIGNAL(src,COMSIG_MAFIA_CAN_PERFORM_ACTION,game,"reveal",current_target) & MAFIA_PREVENT_ACTION || game_status != MAFIA_ALIVE) //Got lynched or roleblocked by a lawyer. current_target = null if(current_target) @@ -218,7 +222,7 @@ role_type = TOWN_INVEST hud_icon = "hudchaplain" revealed_icon = "chaplain" - // winner_award = /datum/award/achievement/mafia/chaplain + winner_award = /datum/award/achievement/mafia/chaplain targeted_actions = list("Pray") var/current_target @@ -238,6 +242,8 @@ current_target = target /datum/mafia_role/chaplain/proc/commune(datum/mafia_controller/game) + SIGNAL_HANDLER + var/datum/mafia_role/target = current_target if(target) to_chat(body,"You invoke spirit of [target.body.real_name] and learn their role was [target.name].") @@ -251,7 +257,7 @@ role_type = TOWN_PROTECT hud_icon = "hudmedicaldoctor" revealed_icon = "medicaldoctor" - // winner_award = /datum/award/achievement/mafia/md + winner_award = /datum/award/achievement/mafia/md targeted_actions = list("Protect") var/datum/mafia_role/current_protected @@ -277,16 +283,22 @@ current_protected = target /datum/mafia_role/md/proc/protect(datum/mafia_controller/game) + SIGNAL_HANDLER + if(current_protected) RegisterSignal(current_protected,COMSIG_MAFIA_ON_KILL,.proc/prevent_kill) add_note("N[game.turn] - Protected [current_protected.body.real_name]") /datum/mafia_role/md/proc/prevent_kill(datum/source) + SIGNAL_HANDLER + to_chat(body,"The person you protected tonight was attacked!") to_chat(current_protected.body,"You were attacked last night, but someone nursed you back to life!") return MAFIA_PREVENT_KILL /datum/mafia_role/md/proc/end_protection(datum/mafia_controller/game) + SIGNAL_HANDLER + if(current_protected) UnregisterSignal(current_protected,COMSIG_MAFIA_ON_KILL) current_protected = null @@ -298,7 +310,7 @@ role_type = TOWN_PROTECT hud_icon = "hudlawyer" revealed_icon = "lawyer" - // winner_award = /datum/award/achievement/mafia/lawyer + winner_award = /datum/award/achievement/mafia/lawyer targeted_actions = list("Advise") var/datum/mafia_role/current_target @@ -310,6 +322,8 @@ RegisterSignal(game,COMSIG_MAFIA_NIGHT_END,.proc/release) /datum/mafia_role/lawyer/proc/roleblock_text(datum/mafia_controller/game) + SIGNAL_HANDLER + if(SEND_SIGNAL(src,COMSIG_MAFIA_CAN_PERFORM_ACTION,game,"roleblock",current_target) & MAFIA_PREVENT_ACTION || game_status != MAFIA_ALIVE) //Got lynched or roleblocked by another lawyer. current_target = null if(current_target) @@ -335,16 +349,22 @@ to_chat(body,"You will block [target.body.real_name] tonight.") /datum/mafia_role/lawyer/proc/try_to_roleblock(datum/mafia_controller/game) + SIGNAL_HANDLER + if(current_target) RegisterSignal(current_target,COMSIG_MAFIA_CAN_PERFORM_ACTION, .proc/prevent_action) /datum/mafia_role/lawyer/proc/release(datum/mafia_controller/game) + SIGNAL_HANDLER + . = ..() if(current_target) UnregisterSignal(current_target, COMSIG_MAFIA_CAN_PERFORM_ACTION) current_target = null /datum/mafia_role/lawyer/proc/prevent_action(datum/source) + SIGNAL_HANDLER + if(game_status == MAFIA_ALIVE) //in case we got killed while imprisoning sk - bad luck edge return MAFIA_PREVENT_ACTION @@ -355,7 +375,7 @@ role_type = TOWN_MISC hud_icon = "hudheadofpersonnel" revealed_icon = "headofpersonnel" - // winner_award = /datum/award/achievement/mafia/hop + winner_award = /datum/award/achievement/mafia/hop targeted_actions = list("Reveal") @@ -378,7 +398,7 @@ role_type = MAFIA_REGULAR hud_icon = "hudchangeling" revealed_icon = "changeling" - // winner_award = /datum/award/achievement/mafia/changeling + winner_award = /datum/award/achievement/mafia/changeling revealed_outfit = /datum/outfit/mafia/changeling special_theme = "syndicate" @@ -389,6 +409,8 @@ RegisterSignal(game,COMSIG_MAFIA_SUNDOWN,.proc/mafia_text) /datum/mafia_role/mafia/proc/mafia_text(datum/mafia_controller/source) + SIGNAL_HANDLER + to_chat(body,"Vote for who to kill tonight. The killer will be chosen randomly from voters.") //better detective for mafia @@ -398,7 +420,7 @@ role_type = MAFIA_SPECIAL hud_icon = "hudthoughtfeeder" revealed_icon = "thoughtfeeder" - // winner_award = /datum/award/achievement/mafia/thoughtfeeder + winner_award = /datum/award/achievement/mafia/thoughtfeeder targeted_actions = list("Learn Role") var/datum/mafia_role/current_investigation @@ -418,6 +440,8 @@ current_investigation = target /datum/mafia_role/mafia/thoughtfeeder/proc/investigate(datum/mafia_controller/game) + SIGNAL_HANDLER + var/datum/mafia_role/target = current_investigation current_investigation = null if(SEND_SIGNAL(src,COMSIG_MAFIA_CAN_PERFORM_ACTION,game,"thoughtfeed",target) & MAFIA_PREVENT_ACTION) @@ -441,7 +465,7 @@ win_condition = "kill everyone." team = MAFIA_TEAM_SOLO role_type = NEUTRAL_KILL - // winner_award = /datum/award/achievement/mafia/traitor + winner_award = /datum/award/achievement/mafia/traitor targeted_actions = list("Night Kill") revealed_outfit = /datum/outfit/mafia/traitor @@ -464,6 +488,8 @@ return TRUE //while alive, town AND mafia cannot win (though since mafia know who is who it's pretty easy to win from that point) /datum/mafia_role/traitor/proc/nightkill_immunity(datum/source,datum/mafia_controller/game,lynch) + SIGNAL_HANDLER + if(game.phase == MAFIA_PHASE_NIGHT && !lynch) to_chat(body,"You were attacked, but they'll have to try harder than that to put you down.") return MAFIA_PREVENT_KILL @@ -481,6 +507,8 @@ to_chat(body,"You will attempt to kill [target.body.real_name] tonight.") /datum/mafia_role/traitor/proc/try_to_kill(datum/mafia_controller/source) + // SIGNAL_HANDLER + var/datum/mafia_role/target = current_victim current_victim = null if(SEND_SIGNAL(src,COMSIG_MAFIA_CAN_PERFORM_ACTION,source,"traitor kill",target) & MAFIA_PREVENT_ACTION) @@ -500,7 +528,7 @@ special_theme = "neutral" hud_icon = "hudnightmare" revealed_icon = "nightmare" - // winner_award = /datum/award/achievement/mafia/nightmare + winner_award = /datum/award/achievement/mafia/nightmare targeted_actions = list("Flicker", "Hunt") var/list/flickering = list() @@ -543,6 +571,8 @@ to_chat(body,"You will hunt everyone in a flickering room down tonight.") /datum/mafia_role/nightmare/proc/flicker_or_hunt(datum/mafia_controller/source) + // SIGNAL_HANDLER + if(game_status != MAFIA_ALIVE || !flicker_target) return if(SEND_SIGNAL(src,COMSIG_MAFIA_CAN_PERFORM_ACTION,source,"nightmare actions",flicker_target) & MAFIA_PREVENT_ACTION) @@ -576,7 +606,7 @@ special_theme = "neutral" hud_icon = "hudfugitive" revealed_icon = "fugitive" - // winner_award = /datum/award/achievement/mafia/fugitive + winner_award = /datum/award/achievement/mafia/fugitive actions = list("Self Preservation") var/charges = 2 @@ -604,11 +634,15 @@ protection_status = !protection_status /datum/mafia_role/fugitive/proc/night_start(datum/mafia_controller/game) + SIGNAL_HANDLER + if(protection_status == FUGITIVE_WILL_PRESERVE) to_chat(body,"Your preparations are complete. Nothing could kill you tonight!") RegisterSignal(src,COMSIG_MAFIA_ON_KILL,.proc/prevent_death) /datum/mafia_role/fugitive/proc/night_end(datum/mafia_controller/game) + SIGNAL_HANDLER + if(protection_status == FUGITIVE_WILL_PRESERVE) charges-- UnregisterSignal(src,COMSIG_MAFIA_ON_KILL) @@ -616,13 +650,16 @@ protection_status = FUGITIVE_NOT_PRESERVING /datum/mafia_role/fugitive/proc/prevent_death(datum/mafia_controller/game) + SIGNAL_HANDLER + to_chat(body,"You were attacked! Luckily, you were ready for this!") return MAFIA_PREVENT_KILL /datum/mafia_role/fugitive/proc/survived(datum/mafia_controller/game) + SIGNAL_HANDLER + if(game_status == MAFIA_ALIVE) - // var/client/winner_client = GLOB.directory[player_key] - // winner_client?.give_award(winner_award, body) + game.award_role(winner_award, src) game.send_message("!! FUGITIVE VICTORY !!") #undef FUGITIVE_NOT_PRESERVING @@ -640,7 +677,7 @@ hud_icon = "hudobsessed" revealed_icon = "obsessed" - // winner_award = /datum/award/achievement/mafia/obsessed + winner_award = /datum/award/achievement/mafia/obsessed revealed_outfit = /datum/outfit/mafia/obsessed // /mafia <- outfit must be readded (just make a new mafia outfits file for all of these) solo_counts_as_town = TRUE //after winning or whatever, can side with whoever. they've already done their objective! @@ -652,6 +689,8 @@ RegisterSignal(game,COMSIG_MAFIA_SUNDOWN,.proc/find_obsession) /datum/mafia_role/obsessed/proc/find_obsession(datum/mafia_controller/game) + SIGNAL_HANDLER + var/list/all_roles_shuffle = shuffle(game.all_roles) for(var/role in all_roles_shuffle) var/datum/mafia_role/possible = role @@ -667,13 +706,14 @@ UnregisterSignal(game,COMSIG_MAFIA_SUNDOWN) /datum/mafia_role/obsessed/proc/check_victory(datum/source,datum/mafia_controller/game,lynch) + SIGNAL_HANDLER + UnregisterSignal(source,COMSIG_MAFIA_ON_KILL) if(game_status == MAFIA_DEAD) return if(lynch) game.send_message("!! OBSESSED VICTORY !!") - // var/client/winner_client = GLOB.directory[player_key] - // winner_client?.give_award(winner_award, body) + game.award_role(winner_award, src) reveal_role(game, FALSE) else to_chat(body, "You have failed your objective to lynch [obsession.body]!") @@ -689,17 +729,18 @@ special_theme = "neutral" hud_icon = "hudclown" revealed_icon = "clown" - // winner_award = /datum/award/achievement/mafia/clown + winner_award = /datum/award/achievement/mafia/clown /datum/mafia_role/clown/New(datum/mafia_controller/game) . = ..() RegisterSignal(src,COMSIG_MAFIA_ON_KILL,.proc/prank) /datum/mafia_role/clown/proc/prank(datum/source,datum/mafia_controller/game,lynch) + // SIGNAL_HANDLER + if(lynch) var/datum/mafia_role/victim = pick(game.judgement_guilty_votes + game.judgement_abstain_votes) game.send_message("[body.real_name] WAS A CLOWN! HONK! They take down [victim.body.real_name] with their last prank.") game.send_message("!! CLOWN VICTORY !!") - // var/client/winner_client = GLOB.directory[player_key] - // winner_client?.give_award(winner_award, body) + game.award_role(winner_award, src) victim.kill(game,FALSE) diff --git a/code/modules/mapping/map_template.dm b/code/modules/mapping/map_template.dm index 087552fada..cc3bfc201e 100644 --- a/code/modules/mapping/map_template.dm +++ b/code/modules/mapping/map_template.dm @@ -196,6 +196,9 @@ deleted_atoms++ log_world("Annihilated [deleted_atoms] objects.") +/datum/map_template/proc/post_load() + return + //for your ever biggening badminnery kevinz000 //❤ - Cyberboss /proc/load_new_z_level(file, name, orientation, list/ztraits) diff --git a/code/modules/mapping/reader.dm b/code/modules/mapping/reader.dm index 11bcc0ffcf..a3e0653678 100644 --- a/code/modules/mapping/reader.dm +++ b/code/modules/mapping/reader.dm @@ -135,7 +135,7 @@ var/curr_z = text2num(dmmRegex.group[5]) if(curr_z < z_lower || curr_z > z_upper) continue - + var/curr_x = text2num(dmmRegex.group[3]) var/curr_y = text2num(dmmRegex.group[4]) @@ -171,7 +171,7 @@ if(width > right_width) for(var/i in 1 to lines) gridLines[i] = copytext(gridLines[i], 1, key_len * right_width) - + // during the actual load we're starting at the top and working our way down gridSet.ycrd += lines - 1 @@ -300,14 +300,14 @@ //we do this after we load everything in. if we don't; we'll have weird atmos bugs regarding atmos adjacent turfs T.AfterChange(CHANGETURF_IGNORE_AIR) + if(did_expand) + world.refresh_atmos_grid() + #ifdef TESTING if(turfsSkipped) testing("Skipped loading [turfsSkipped] default turfs") #endif - if(did_expand) - world.refresh_atmos_grid() - return TRUE /datum/parsed_map/proc/build_cache(no_changeturf, bad_paths=null) diff --git a/code/modules/mapping/verify.dm b/code/modules/mapping/verify.dm index 1f071aaec7..7fe07d3b9e 100644 --- a/code/modules/mapping/verify.dm +++ b/code/modules/mapping/verify.dm @@ -1,4 +1,4 @@ -/// An error report generated by [parsed_map/check_for_errors]. +/// An error report generated by [/datum/parsed_map/proc/check_for_errors]. /datum/map_report var/original_path var/list/bad_paths = list() diff --git a/code/modules/mining/abandoned_crates.dm b/code/modules/mining/abandoned_crates.dm index 8c9b0b53e1..e97060b45d 100644 --- a/code/modules/mining/abandoned_crates.dm +++ b/code/modules/mining/abandoned_crates.dm @@ -190,7 +190,7 @@ /obj/structure/closet/crate/secure/loot/attackby(obj/item/W, mob/user) if(locked) - if(istype(W, /obj/item/multitool)) + if(W.tool_behaviour == TOOL_MULTITOOL) to_chat(user, "DECA-CODE LOCK REPORT:") if(attempts == 1) to_chat(user, "* Anti-Tamper Bomb will activate on next failed access attempt.") diff --git a/code/modules/mining/aux_base.dm b/code/modules/mining/aux_base.dm index b10177ebaf..feb4b4b2da 100644 --- a/code/modules/mining/aux_base.dm +++ b/code/modules/mining/aux_base.dm @@ -25,7 +25,7 @@ interface with the mining shuttle at the landing site if a mobile beacon is also var/possible_destinations clockwork = TRUE var/obj/item/gps/internal/base/locator - circuit = /obj/item/circuitboard/computer/auxillary_base + circuit = /obj/item/circuitboard/computer/auxiliary_base /obj/machinery/computer/auxillary_base/Initialize() . = ..() diff --git a/code/modules/mining/equipment/kinetic_crusher.dm b/code/modules/mining/equipment/kinetic_crusher.dm index a6f456ce6f..859ea58d26 100644 --- a/code/modules/mining/equipment/kinetic_crusher.dm +++ b/code/modules/mining/equipment/kinetic_crusher.dm @@ -65,7 +65,7 @@ . += "It has \a [T] attached, which causes [T.effect_desc()]." /obj/item/kinetic_crusher/attackby(obj/item/I, mob/living/user) - if(istype(I, /obj/item/crowbar)) + if(I.tool_behaviour == TOOL_CROWBAR) if(LAZYLEN(trophies)) to_chat(user, "You remove [src]'s trophies.") I.play_tool_sound(src) diff --git a/code/modules/mining/laborcamp/laborstacker.dm b/code/modules/mining/laborcamp/laborstacker.dm index 2ca4d5fd2e..6ef4684921 100644 --- a/code/modules/mining/laborcamp/laborstacker.dm +++ b/code/modules/mining/laborcamp/laborstacker.dm @@ -8,12 +8,12 @@ GLOBAL_LIST(labor_sheet_values) icon = 'icons/obj/machines/mining_machines.dmi' icon_state = "console" density = FALSE - + /// Connected stacking machine var/obj/machinery/mineral/stacking_machine/laborstacker/stacking_machine = null + /// Direction of the stacking machine var/machinedir = SOUTH - var/obj/machinery/door/airlock/release_door - var/door_tag = "prisonshuttle" - var/obj/item/radio/Radio //needed to send messages to sec radio + /// Needed to send messages to sec radio + var/obj/item/radio/Radio /obj/machinery/mineral/labor_claim_console/Initialize() . = ..() @@ -39,15 +39,23 @@ GLOBAL_LIST(labor_sheet_values) ui = new(user, src, "LaborClaimConsole", name) ui.open() +/obj/machinery/mineral/labor_claim_console/ui_static_data(mob/user) + var/list/data = list() + data["ores"] = GLOB.labor_sheet_values + return data + /obj/machinery/mineral/labor_claim_console/ui_data(mob/user) var/list/data = list() var/can_go_home = FALSE - data["emagged"] = (obj_flags & EMAGGED) ? 1 : 0 + data["emagged"] = FALSE if(obj_flags & EMAGGED) + data["emagged"] = TRUE can_go_home = TRUE - - var/obj/item/card/id/I = user.get_idcard(TRUE) + var/obj/item/card/id/I + if(isliving(usr)) + var/mob/living/L = usr + I = L.get_idcard(TRUE) if(istype(I, /obj/item/card/id/prisoner)) var/obj/item/card/id/prisoner/P = I data["id_points"] = P.points @@ -63,43 +71,46 @@ GLOBAL_LIST(labor_sheet_values) if(stacking_machine) data["unclaimed_points"] = stacking_machine.points - data["ores"] = GLOB.labor_sheet_values data["can_go_home"] = can_go_home - return data /obj/machinery/mineral/labor_claim_console/ui_act(action, params) - if(..()) + . = ..() + if(.) return + + var/mob/M = usr switch(action) if("claim_points") - var/mob/M = usr - var/obj/item/card/id/I = M.get_idcard(TRUE) + var/obj/item/card/id/I + if(isliving(M)) + var/mob/living/L = M + I = L.get_idcard(TRUE) if(istype(I, /obj/item/card/id/prisoner)) var/obj/item/card/id/prisoner/P = I P.points += stacking_machine.points stacking_machine.points = 0 - to_chat(usr, "Points transferred.") - . = TRUE + to_chat(M, "Points transferred.") + return TRUE else - to_chat(usr, "No valid id for point transfer detected.") + to_chat(M, "No valid id for point transfer detected.") if("move_shuttle") - if(!alone_in_area(get_area(src), usr)) - to_chat(usr, "Prisoners are only allowed to be released while alone.") - else - switch(SSshuttle.moveShuttle("laborcamp", "laborcamp_home", TRUE)) - if(1) - to_chat(usr, "Shuttle not found.") - if(2) - to_chat(usr, "Shuttle already at station.") - if(3) - to_chat(usr, "No permission to dock could be granted.") - else - if(!(obj_flags & EMAGGED)) - Radio.set_frequency(FREQ_SECURITY) - Radio.talk_into(src, "A prisoner has returned to the station. Minerals and Prisoner ID card ready for retrieval.", FREQ_SECURITY) - to_chat(usr, "Shuttle received message and will be sent shortly.") - . = TRUE + if(!alone_in_area(get_area(src), M)) + to_chat(M, "Prisoners are only allowed to be released while alone.") + return + switch(SSshuttle.moveShuttle("laborcamp", "laborcamp_home", TRUE)) + if(1) + to_chat(M, "Shuttle not found.") + if(2) + to_chat(M, "Shuttle already at station.") + if(3) + to_chat(M, "No permission to dock could be granted.") + else + if(!(obj_flags & EMAGGED)) + Radio.set_frequency(FREQ_SECURITY) + Radio.talk_into(src, "A prisoner has returned to the station. Minerals and Prisoner ID card ready for retrieval.", FREQ_SECURITY) + to_chat(M, "Shuttle received message and will be sent shortly.") + return TRUE /obj/machinery/mineral/labor_claim_console/proc/locate_stacking_machine() stacking_machine = locate(/obj/machinery/mineral/stacking_machine, get_step(src, machinedir)) @@ -110,10 +121,9 @@ GLOBAL_LIST(labor_sheet_values) /obj/machinery/mineral/labor_claim_console/emag_act(mob/user) . = ..() - if((obj_flags & EMAGGED)) - return - obj_flags |= EMAGGED - to_chat(user, "PZZTTPFFFT") + if(!(obj_flags & EMAGGED)) + obj_flags |= EMAGGED + to_chat(user, "PZZTTPFFFT") return TRUE /**********************Prisoner Collection Unit**************************/ @@ -121,13 +131,13 @@ GLOBAL_LIST(labor_sheet_values) /obj/machinery/mineral/stacking_machine/laborstacker force_connect = TRUE var/points = 0 //The unclaimed value of ore stacked. - //damage_deflection = 21 + // damage_deflection = 21 /obj/machinery/mineral/stacking_machine/laborstacker/process_sheet(obj/item/stack/sheet/inp) points += inp.point_value * inp.amount ..() /obj/machinery/mineral/stacking_machine/laborstacker/attackby(obj/item/I, mob/living/user) - if(istype(I, /obj/item/stack/sheet) && user.canUnEquip(I)) + if(istype(I, /obj/item/stack/sheet) && user.canUnEquip(I) && user.a_intent == INTENT_HELP) var/obj/item/stack/sheet/inp = I points += inp.point_value * inp.amount return ..() @@ -141,7 +151,10 @@ GLOBAL_LIST(labor_sheet_values) icon_state = "console" density = FALSE -/obj/machinery/mineral/labor_points_checker/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags) +/obj/machinery/mineral/labor_points_checker/attack_hand(mob/user) + . = ..() + if(. || user.is_blind()) + return user.examinate(src) /obj/machinery/mineral/labor_points_checker/attackby(obj/item/I, mob/user, params) diff --git a/code/modules/mining/lavaland/necropolis_chests.dm b/code/modules/mining/lavaland/necropolis_chests.dm index 9c39601b4e..6af1c2118c 100644 --- a/code/modules/mining/lavaland/necropolis_chests.dm +++ b/code/modules/mining/lavaland/necropolis_chests.dm @@ -522,7 +522,7 @@ max_charges = 1 item_flags = NEEDS_PERMIT | NOBLUDGEON w_class = WEIGHT_CLASS_BULKY - force = 18 + force = 15 /obj/item/ammo_casing/magic/hook name = "hook" @@ -536,11 +536,11 @@ icon_state = "hook" icon = 'icons/obj/lavaland/artefacts.dmi' pass_flags = PASSTABLE - damage = 25 - armour_penetration = 100 + damage = 15 + armour_penetration = 10 + knockdown = 5 damage_type = BRUTE hitsound = 'sound/effects/splat.ogg' - knockdown = 30 var/chain /obj/item/projectile/hook/fire(setAngle) diff --git a/code/modules/mining/machine_redemption.dm b/code/modules/mining/machine_redemption.dm index 0268d32a6b..6cba540ca0 100644 --- a/code/modules/mining/machine_redemption.dm +++ b/code/modules/mining/machine_redemption.dm @@ -188,8 +188,10 @@ return ..() -/obj/machinery/mineral/ore_redemption/multitool_act(mob/living/user, obj/item/multitool/I) - if (panel_open) +/obj/machinery/mineral/ore_redemption/multitool_act(mob/living/user, obj/item/I) + if(!I.tool_behaviour == TOOL_MULTITOOL) + return + if(panel_open) input_dir = turn(input_dir, -90) output_dir = turn(output_dir, -90) to_chat(user, "You change [src]'s I/O settings, setting the input to [dir2text(input_dir)] and the output to [dir2text(output_dir)].") @@ -250,6 +252,8 @@ if(points) if(I) I.mining_points += points + if(usr.client) + usr.client.increment_progress("miner", points) points = 0 else to_chat(usr, "No ID detected.") diff --git a/code/modules/mining/machine_silo.dm b/code/modules/mining/machine_silo.dm index 09246abc46..34b349b198 100644 --- a/code/modules/mining/machine_silo.dm +++ b/code/modules/mining/machine_silo.dm @@ -47,7 +47,7 @@ GLOBAL_LIST_EMPTY(silo_access_logs) return ..() -/obj/machinery/ore_silo/proc/remote_attackby(obj/machinery/M, mob/user, obj/item/stack/I) +/obj/machinery/ore_silo/proc/remote_attackby(obj/machinery/M, mob/user, obj/item/stack/I, remote = null) var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) // stolen from /datum/component/material_container/proc/OnAttackBy if(user.a_intent != INTENT_HELP) @@ -63,7 +63,7 @@ GLOBAL_LIST_EMPTY(silo_access_logs) return // assumes unlimited space... var/amount = I.amount - materials.user_insert(I, user) + materials.user_insert(I, user, remote) silo_log(M, "deposited", amount, "sheets", item_mats) return TRUE @@ -170,8 +170,8 @@ GLOBAL_LIST_EMPTY(silo_access_logs) updateUsrDialog() return TRUE -/obj/machinery/ore_silo/multitool_act(mob/living/user, obj/item/multitool/I) - if (istype(I)) +/obj/machinery/ore_silo/multitool_act(mob/living/user, obj/item/I) + if(I.tool_behaviour == TOOL_MULTITOOL) to_chat(user, "You log [src] in the multitool's buffer.") I.buffer = src return TRUE diff --git a/code/modules/mining/machine_stacking.dm b/code/modules/mining/machine_stacking.dm index a5ff27e75e..40f4db3660 100644 --- a/code/modules/mining/machine_stacking.dm +++ b/code/modules/mining/machine_stacking.dm @@ -7,7 +7,9 @@ desc = "Controls a stacking machine... in theory." density = FALSE circuit = /obj/item/circuitboard/machine/stacking_unit_console + /// Connected stacking machine var/obj/machinery/mineral/stacking_machine/machine + /// Direction for which console looks for stacking machine to connect to var/machinedir = SOUTHEAST /obj/machinery/mineral/stacking_unit_console/Initialize() @@ -16,50 +18,53 @@ if (machine) machine.CONSOLE = src -/obj/machinery/mineral/stacking_unit_console/ui_interact(mob/user) - . = ..() - - if(!machine) - to_chat(user, "[src] is not linked to a machine!") - return - - var/obj/item/stack/sheet/s - var/dat - - dat += text("Stacking unit console

    ") - - for(var/O in machine.stack_list) - s = machine.stack_list[O] - if(s.amount > 0) - dat += text("[capitalize(s.name)]: [s.amount] Release
    ") - - dat += text("
    Stacking: [machine.stack_amt]

    ") - - user << browse(dat, "window=console_stacking_machine") - /obj/machinery/mineral/stacking_unit_console/multitool_act(mob/living/user, obj/item/I) - if(istype(I, /obj/item/multitool)) - var/obj/item/multitool/M = I - M.buffer = src - to_chat(user, "You store linkage information in [I]'s buffer.") - return TRUE - -/obj/machinery/mineral/stacking_unit_console/Topic(href, href_list) - if(..()) + if(!multitool_check_buffer(user, I)) return - usr.set_machine(src) - src.add_fingerprint(usr) - if(href_list["release"]) - if(!(text2path(href_list["release"]) in machine.stack_list)) - return //someone tried to spawn materials by spoofing hrefs - var/obj/item/stack/sheet/inp = machine.stack_list[text2path(href_list["release"])] - var/obj/item/stack/sheet/out = new inp.type(null, inp.amount) - inp.amount = 0 - machine.unload_mineral(out) + var/obj/item/multitool/M = I + M.buffer = src + to_chat(user, "You store linkage information in [I]'s buffer.") + return TRUE - src.updateUsrDialog() - return +/obj/machinery/mineral/stacking_unit_console/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "StackingConsole", name) + ui.open() +/obj/machinery/mineral/stacking_unit_console/ui_data(mob/user) + var/list/data = list() + data["machine"] = machine ? TRUE : FALSE + data["stacking_amount"] = null + data["contents"] = list() + if(machine) + data["stacking_amount"] = machine.stack_amt + for(var/stack_type in machine.stack_list) + var/obj/item/stack/sheet/stored_sheet = machine.stack_list[stack_type] + if(stored_sheet.amount <= 0) + continue + data["contents"] += list(list( + "type" = stored_sheet.type, + "name" = capitalize(stored_sheet.name), + "amount" = stored_sheet.amount, + )) + return data + +/obj/machinery/mineral/stacking_unit_console/ui_act(action, list/params) + . = ..() + if(.) + return + + switch(action) + if("release") + var/obj/item/stack/sheet/released_type = text2path(params["type"]) + if(!released_type || !(initial(released_type.merge_type) in machine.stack_list)) + return //someone tried to spawn materials by spoofing hrefs + var/obj/item/stack/sheet/inp = machine.stack_list[initial(released_type.merge_type)] + var/obj/item/stack/sheet/out = new inp.type(null, inp.amount) + inp.amount = 0 + machine.unload_mineral(out) + return TRUE /**********************Mineral stacking unit**************************/ @@ -97,8 +102,8 @@ if(istype(AM, /obj/item/stack/sheet) && AM.loc == get_step(src, input_dir)) process_sheet(AM) -/obj/machinery/mineral/stacking_machine/multitool_act(mob/living/user, obj/item/multitool/M) - if(istype(M)) +/obj/machinery/mineral/stacking_machine/multitool_act(mob/living/user, obj/item/M) + if(M.tool_behaviour == TOOL_MULTITOOL) if(istype(M.buffer, /obj/machinery/mineral/stacking_unit_console)) CONSOLE = M.buffer CONSOLE.machine = src @@ -108,6 +113,17 @@ /obj/machinery/mineral/stacking_machine/proc/process_sheet(obj/item/stack/sheet/inp) if(QDELETED(inp)) return + + // Dump the sheets to the silo if attached + if(materials.silo && !materials.on_hold()) + var/matlist = inp.custom_materials & materials.mat_container.materials + if (length(matlist)) + var/inserted = materials.mat_container.insert_item(inp) + materials.silo_log(src, "collected", inserted, "sheets", matlist) + qdel(inp) + return + + // No silo attached process to internal storage var/key = inp.merge_type var/obj/item/stack/sheet/storage = stack_list[key] if(!storage) //It's the first of this sheet added @@ -115,15 +131,6 @@ storage.amount += inp.amount //Stack the sheets qdel(inp) - if(materials.silo && !materials.on_hold()) //Dump the sheets to the silo - var/matlist = storage.custom_materials & materials.mat_container.materials - if (length(matlist)) - var/inserted = materials.mat_container.insert_item(storage) - materials.silo_log(src, "collected", inserted, "sheets", matlist) - if (QDELETED(storage)) - stack_list -= key - return - while(storage.amount >= stack_amt) //Get rid of excessive stackage var/obj/item/stack/sheet/out = new inp.type(null, stack_amt) unload_mineral(out) diff --git a/code/modules/mining/minebot.dm b/code/modules/mining/minebot.dm index 627d79d6ba..8037a22a52 100644 --- a/code/modules/mining/minebot.dm +++ b/code/modules/mining/minebot.dm @@ -105,7 +105,7 @@ to_chat(user, "You instruct [src] to drop any collected ore.") DropOre() return - if(istype(I, /obj/item/crowbar) || istype(I, /obj/item/borg/upgrade/modkit)) + if(I.tool_behaviour == TOOL_CROWBAR || istype(I, /obj/item/borg/upgrade/modkit)) I.melee_attack_chain(user, stored_gun, params) return ..() diff --git a/code/modules/mining/ores_coins.dm b/code/modules/mining/ores_coins.dm index 26a3ca1b70..a3399bf46c 100644 --- a/code/modules/mining/ores_coins.dm +++ b/code/modules/mining/ores_coins.dm @@ -251,7 +251,7 @@ GLOBAL_LIST_INIT(sand_recipes, list(\ GibtoniteReaction(user) return if(primed) - if(istype(I, /obj/item/mining_scanner) || istype(I, /obj/item/t_scanner/adv_mining_scanner) || istype(I, /obj/item/multitool)) + if(istype(I, /obj/item/mining_scanner) || istype(I, /obj/item/t_scanner/adv_mining_scanner) || I.tool_behaviour == TOOL_MULTITOOL) primed = FALSE if(det_timer) deltimer(det_timer) diff --git a/code/modules/mining/point_bank.dm b/code/modules/mining/point_bank.dm index f18b62635f..f467b94de5 100644 --- a/code/modules/mining/point_bank.dm +++ b/code/modules/mining/point_bank.dm @@ -20,25 +20,6 @@ data["totalPoints"] = points return data -/obj/machinery/mineral/ore_redemption/ui_act(action, params) - if(..()) - return - switch(action) - if("Claim") - var/mob/M = usr - var/obj/item/card/id/I = M.get_idcard(TRUE) - if(points) - if(I) - I.mining_points += points - if(usr.client) - usr.client.increment_progress("miner", points) - points = 0 - else - to_chat(usr, "No ID detected.") - else - to_chat(usr, "No points to claim.") - return TRUE - /obj/machinery/point_bank/power_change() ..() update_icon() diff --git a/code/modules/mob/dead/dead.dm b/code/modules/mob/dead/dead.dm index 87f9d716bd..223366c7ab 100644 --- a/code/modules/mob/dead/dead.dm +++ b/code/modules/mob/dead/dead.dm @@ -8,6 +8,7 @@ INITIALIZE_IMMEDIATE(/mob/dead) throwforce = 0 /mob/dead/Initialize() + SHOULD_CALL_PARENT(FALSE) if(flags_1 & INITIALIZED_1) stack_trace("Warning: [src]([type]) initialized multiple times!") flags_1 |= INITIALIZED_1 @@ -68,14 +69,15 @@ INITIALIZE_IMMEDIATE(/mob/dead) set desc= "Jump to the other server" if(mob_transforming) return - var/list/csa = CONFIG_GET(keyed_list/cross_server) + var/list/our_id = CONFIG_GET(string/cross_comms_name) + var/list/csa = CONFIG_GET(keyed_list/cross_server) - our_id var/pick switch(csa.len) if(0) remove_verb(src, /mob/dead/proc/server_hop) to_chat(src, "Server Hop has been disabled.") if(1) - pick = csa[0] + pick = csa[1] else pick = input(src, "Pick a server to jump to", "Server Hop") as null|anything in csa @@ -100,7 +102,7 @@ INITIALIZE_IMMEDIATE(/mob/dead) winset(src, null, "command=.options") //other wise the user never knows if byond is downloading resources - C << link("[addr]?server_hop=[key]") + C << link("[addr]") /mob/dead/proc/update_z(new_z) // 1+ to register, null to unregister if (registered_z != new_z) diff --git a/code/modules/mob/dead/new_player/new_player.dm b/code/modules/mob/dead/new_player/new_player.dm index 00c22ad96d..11f1d58c0c 100644 --- a/code/modules/mob/dead/new_player/new_player.dm +++ b/code/modules/mob/dead/new_player/new_player.dm @@ -54,24 +54,7 @@ output += "

    [LINKIFY_READY("Observe", PLAYER_READY_TO_OBSERVE)]

    " if(!IsGuestKey(src.key)) - if (SSdbcore.Connect()) - var/isadmin = 0 - if(src.client && src.client.holder) - isadmin = 1 - var/datum/DBQuery/query_get_new_polls = SSdbcore.NewQuery("SELECT id FROM [format_table_name("poll_question")] WHERE [(isadmin ? "" : "adminonly = false AND")] Now() BETWEEN starttime AND endtime AND id NOT IN (SELECT pollid FROM [format_table_name("poll_vote")] WHERE ckey = \"[sanitizeSQL(ckey)]\") AND id NOT IN (SELECT pollid FROM [format_table_name("poll_textreply")] WHERE ckey = \"[sanitizeSQL(ckey)]\")") - var/rs = REF(src) - if(query_get_new_polls.Execute()) - var/newpoll = 0 - if(query_get_new_polls.NextRow()) - newpoll = 1 - - if(newpoll) - output += "

    Show Player Polls (NEW!)

    " - else - output += "

    Show Player Polls

    " - qdel(query_get_new_polls) - if(QDELETED(src)) - return + output += playerpolls() output += "" @@ -81,6 +64,41 @@ popup.set_content(output) popup.open(FALSE) +/mob/dead/new_player/proc/playerpolls() + var/output = "" //hey tg why is this a list? + if (SSdbcore.Connect()) + var/isadmin = FALSE + if(client?.holder) + isadmin = TRUE + var/datum/db_query/query_get_new_polls = SSdbcore.NewQuery({" + SELECT id FROM [format_table_name("poll_question")] + WHERE (adminonly = 0 OR :isadmin = 1) + AND Now() BETWEEN starttime AND endtime + AND deleted = 0 + AND id NOT IN ( + SELECT pollid FROM [format_table_name("poll_vote")] + WHERE ckey = :ckey + AND deleted = 0 + ) + AND id NOT IN ( + SELECT pollid FROM [format_table_name("poll_textreply")] + WHERE ckey = :ckey + AND deleted = 0 + ) + "}, list("isadmin" = isadmin, "ckey" = ckey)) + var/rs = REF(src) + if(!query_get_new_polls.Execute()) + qdel(query_get_new_polls) + return + if(query_get_new_polls.NextRow()) + output += "

    Show Player Polls (NEW!)

    " + else + output += "

    Show Player Polls

    " + qdel(query_get_new_polls) + if(QDELETED(src)) + return + return output + /mob/dead/new_player/proc/age_gate() var/list/dat = list("
    ") dat += "Enter your date of birth here, to confirm that you are over 18.
    " @@ -123,23 +141,76 @@ if(!client.set_db_player_flags()) message_admins("Blocked [src] from new player panel because age gate could not access player database flags.") return FALSE - else - var/dbflags = client.prefs.db_flags - if(dbflags & DB_FLAG_AGE_CONFIRMATION_INCOMPLETE) //they have not completed age gate - var/age_verification = age_gate() - if(age_verification != 1) - client.add_system_note("Automated-Age-Gate", "Failed automatic age gate process") - //ban them and kick them - AddBan(client.ckey, client.computer_id, "SYSTEM BAN - Inputted date during join verification was under 18 years of age. Contact administration on discord for verification.", "SYSTEM", FALSE, null, client.address) - qdel(client) - return FALSE - else - //they claim to be of age, so allow them to continue and update their flags - client.update_flag_db(DB_FLAG_AGE_CONFIRMATION_COMPLETE, TRUE) - client.update_flag_db(DB_FLAG_AGE_CONFIRMATION_INCOMPLETE, FALSE) - //log this - message_admins("[ckey] has joined through the automated age gate process.") - return TRUE + + if(!(client.prefs.db_flags & DB_FLAG_AGE_CONFIRMATION_INCOMPLETE)) //completed? Skip + return TRUE + + var/age_verification = age_gate() + //ban them and kick them + if(age_verification != 1) + // this isn't code, this is paragraphs. + var/player_ckey = ckey(client.ckey) + // record all admins and non-admins online at the time + var/list/clients_online = GLOB.clients.Copy() + var/list/admins_online = GLOB.admins.Copy() //list() // remove the GLOB.admins.Copy() and the comments if you want the pure admins_online check + // for(var/client/C in clients_online) + // if(C.holder) //deadmins aren't included since they wouldn't show up on adminwho + // admins_online += C + var/who = clients_online.Join(", ") + var/adminwho = admins_online.Join(", ") + + var/datum/db_query/query_add_ban = SSdbcore.NewQuery({" + INSERT INTO [format_table_name("ban")] + (bantime, server_ip, server_port , round_id, bantype, reason, job, duration, expiration_time, ckey, computerid, ip, a_ckey, a_computerid, a_ip, who, adminwho) + VALUES (Now(), INET_ATON(:server_ip), :server_port, :round_id, :bantype_str, :reason, :role, :duration, Now() + INTERVAL :duration MINUTE, :ckey, :computerid, INET_ATON(:ip), :a_ckey, :a_computerid, INET_ATON(:a_ip), :who, :adminwho)"}, + list( + // Server info + "server_ip" = world.internet_address || 0, + "server_port" = world.port, + "round_id" = GLOB.round_id, + // Client ban info + "bantype_str" = "ADMIN_PERMABAN", + "reason" = "SYSTEM BAN - Inputted date during join verification was under 18 years of age. Contact administration on discord for verification.", + "role" = null, + "duration" = -1, + "ckey" = player_ckey, + "ip" = client.address || null, + "computerid" = client.computer_id || null, + // Admin banning info + "a_ckey" = "SYSTEM (Automated-Age-Gate)", // the server + "a_ip" = null, //key_name + "a_computerid" = "0", + "who" = who, + "adminwho" = adminwho + )) + + client.add_system_note("Automated-Age-Gate", "Failed automatic age gate process.") + if(!query_add_ban.Execute()) + // this is the part where you should panic. + qdel(query_add_ban) + message_admins("WARNING! Failed to ban [ckey] for failing the automatic age gate.") + send2tgs_adminless_only("WARNING! Failed to ban [ckey] for failing the automatic age gate.") + qdel(client) + return FALSE + qdel(query_add_ban) + + create_message("note", player_ckey, "SYSTEM (Automated-Age-Gate)", "SYSTEM BAN - Inputted date during join verification was under 18 years of age. Contact administration on discord for verification.", null, null, 0, 0, null, 0, "high") + + // announce this + message_admins("[ckey] has been banned for failing the automatic age gate.") + send2tgs_adminless_only("[ckey] has been banned for failing the automatic age gate.") + + // removing the client disconnects them + qdel(client) + + return FALSE + + //they claim to be of age, so allow them to continue and update their flags + client.update_flag_db(DB_FLAG_AGE_CONFIRMATION_COMPLETE, TRUE) + client.update_flag_db(DB_FLAG_AGE_CONFIRMATION_INCOMPLETE, FALSE) + //log this + message_admins("[ckey] has joined through the automated age gate process.") + return TRUE /mob/dead/new_player/Topic(href, href_list[]) diff --git a/code/modules/mob/dead/new_player/poll.dm b/code/modules/mob/dead/new_player/poll.dm index 84f5e97a3b..9b6e22bc23 100644 --- a/code/modules/mob/dead/new_player/poll.dm +++ b/code/modules/mob/dead/new_player/poll.dm @@ -4,9 +4,13 @@ /mob/dead/new_player/proc/handle_player_polling() if(!SSdbcore.IsConnected()) - to_chat(usr, "Failed to establish database connection.") + to_chat(usr, "Failed to establish database connection.", confidential = TRUE) return - var/datum/DBQuery/query_poll_get = SSdbcore.NewQuery("SELECT id, question FROM [format_table_name("poll_question")] WHERE Now() BETWEEN starttime AND endtime [(client.holder ? "" : "AND adminonly = false")]") + var/datum/db_query/query_poll_get = SSdbcore.NewQuery({" + SELECT id, question + FROM [format_table_name("poll_question")] + WHERE Now() BETWEEN starttime AND endtime [(client.holder ? "" : "AND adminonly = false")] + "}) if(!query_poll_get.warn_execute()) qdel(query_poll_get) return @@ -27,9 +31,15 @@ if(!pollid) return if (!SSdbcore.Connect()) - to_chat(usr, "Failed to establish database connection.") + to_chat(usr, "Failed to establish database connection.", confidential = TRUE) return - var/datum/DBQuery/query_poll_get_details = SSdbcore.NewQuery("SELECT starttime, endtime, question, polltype, multiplechoiceoptions FROM [format_table_name("poll_question")] WHERE id = [pollid]") + var/datum/db_query/query_poll_get_details = SSdbcore.NewQuery({" + SELECT starttime, endtime, question, polltype, multiplechoiceoptions + FROM [format_table_name("poll_question")] + WHERE id = :id + "}, list( + "id" = pollid + )) if(!query_poll_get_details.warn_execute()) qdel(query_poll_get_details) return @@ -47,7 +57,14 @@ qdel(query_poll_get_details) switch(polltype) if(POLLTYPE_OPTION) - var/datum/DBQuery/query_option_get_votes = SSdbcore.NewQuery("SELECT optionid FROM [format_table_name("poll_vote")] WHERE pollid = [pollid] AND ckey = '[ckey]'") + var/datum/db_query/query_option_get_votes = SSdbcore.NewQuery({" + SELECT optionid + FROM [format_table_name("poll_vote")] + WHERE pollid = :id AND ckey = :ckey + "}, list( + "id" = pollid, + "ckey" = ckey + )) if(!query_option_get_votes.warn_execute()) qdel(query_option_get_votes) return @@ -56,7 +73,13 @@ votedoptionid = text2num(query_option_get_votes.item[1]) qdel(query_option_get_votes) var/list/datum/polloption/options = list() - var/datum/DBQuery/query_option_options = SSdbcore.NewQuery("SELECT id, text FROM [format_table_name("poll_option")] WHERE pollid = [pollid]") + var/datum/db_query/query_option_options = SSdbcore.NewQuery({" + SELECT id, text + FROM [format_table_name("poll_option")] + WHERE pollid = :id + "}, list( + "id" = pollid + )) if(!query_option_options.warn_execute()) qdel(query_option_options) return @@ -92,7 +115,14 @@ src << browse(null ,"window=playerpolllist") src << browse(output,"window=playerpoll;size=500x250") if(POLLTYPE_TEXT) - var/datum/DBQuery/query_text_get_votes = SSdbcore.NewQuery("SELECT replytext FROM [format_table_name("poll_textreply")] WHERE pollid = [pollid] AND ckey = '[ckey]'") + var/datum/db_query/query_text_get_votes = SSdbcore.NewQuery({" + SELECT replytext + FROM [format_table_name("poll_textreply")] + WHERE pollid = :id AND ckey = :ckey + "}, list( + "id" = pollid, + "ckey" = ckey + )) if(!query_text_get_votes.warn_execute()) qdel(query_text_get_votes) return @@ -120,7 +150,13 @@ src << browse(null ,"window=playerpolllist") src << browse(output,"window=playerpoll;size=500x500") if(POLLTYPE_RATING) - var/datum/DBQuery/query_rating_get_votes = SSdbcore.NewQuery("SELECT o.text, v.rating FROM [format_table_name("poll_option")] o, [format_table_name("poll_vote")] v WHERE o.pollid = [pollid] AND v.ckey = '[ckey]' AND o.id = v.optionid") + var/datum/db_query/query_rating_get_votes = SSdbcore.NewQuery({" + SELECT o.text, v.rating FROM [format_table_name("poll_option")] o, [format_table_name("poll_vote")] v + WHERE o.pollid = :id AND v.ckey = :ckey AND o.id = v.optionid + "}, list( + "id" = pollid, + "ckey" = ckey + )) if(!query_rating_get_votes.warn_execute()) qdel(query_rating_get_votes) return @@ -140,7 +176,13 @@ output += "" var/minid = 999999 var/maxid = 0 - var/datum/DBQuery/query_rating_options = SSdbcore.NewQuery("SELECT id, text, minval, maxval, descmin, descmid, descmax FROM [format_table_name("poll_option")] WHERE pollid = [pollid]") + var/datum/db_query/query_rating_options = SSdbcore.NewQuery({" + SELECT id, text, minval, maxval, descmin, descmid, descmax + FROM [format_table_name("poll_option")] + WHERE pollid = :id + "}, list( + "id" = pollid + )) if(!query_rating_options.warn_execute()) qdel(query_rating_options) return @@ -177,7 +219,14 @@ src << browse(null ,"window=playerpolllist") src << browse(output,"window=playerpoll;size=500x500") if(POLLTYPE_MULTI) - var/datum/DBQuery/query_multi_get_votes = SSdbcore.NewQuery("SELECT optionid FROM [format_table_name("poll_vote")] WHERE pollid = [pollid] AND ckey = '[ckey]'") + var/datum/db_query/query_multi_get_votes = SSdbcore.NewQuery({" + SELECT optionid + FROM [format_table_name("poll_vote")] + WHERE pollid = :id AND ckey = :ckey + "}, list( + "id" = pollid, + "ckey" = ckey + )) if(!query_multi_get_votes.warn_execute()) qdel(query_multi_get_votes) return @@ -188,7 +237,13 @@ var/list/datum/polloption/options = list() var/maxoptionid = 0 var/minoptionid = 0 - var/datum/DBQuery/query_multi_options = SSdbcore.NewQuery("SELECT id, text FROM [format_table_name("poll_option")] WHERE pollid = [pollid]") + var/datum/db_query/query_multi_options = SSdbcore.NewQuery({" + SELECT id, text + FROM [format_table_name("poll_option")] + WHERE pollid = :id + "}, list( + "id" = pollid + )) if(!query_multi_options.warn_execute()) qdel(query_multi_options) return @@ -231,8 +286,10 @@ if(POLLTYPE_IRV) var/datum/asset/irv_assets = get_asset_datum(/datum/asset/group/irv) irv_assets.send(src) - - var/datum/DBQuery/query_irv_get_votes = SSdbcore.NewQuery("SELECT optionid FROM [format_table_name("poll_vote")] WHERE pollid = [pollid] AND ckey = '[ckey]'") + var/datum/db_query/query_irv_get_votes = SSdbcore.NewQuery({" + SELECT optionid FROM [format_table_name("poll_vote")] + WHERE pollid = :pollid AND ckey = :ckey AND deleted = 0 + "}, list("pollid" = pollid, "ckey" = ckey)) if(!query_irv_get_votes.warn_execute()) qdel(query_irv_get_votes) return @@ -244,7 +301,13 @@ var/list/datum/polloption/options = list() - var/datum/DBQuery/query_irv_options = SSdbcore.NewQuery("SELECT id, text FROM [format_table_name("poll_option")] WHERE pollid = [pollid]") + var/datum/db_query/query_irv_options = SSdbcore.NewQuery({" + SELECT id, text + FROM [format_table_name("poll_option")] + WHERE pollid = :id + "}, list( + "id" = pollid + )) if(!query_irv_options.warn_execute()) qdel(query_irv_options) return @@ -352,16 +415,23 @@ if (text) table = "poll_textreply" if (!SSdbcore.Connect()) - to_chat(usr, "Failed to establish database connection.") + to_chat(usr, "Failed to establish database connection.", confidential = TRUE) return - var/datum/DBQuery/query_hasvoted = SSdbcore.NewQuery("SELECT id FROM `[format_table_name(table)]` WHERE pollid = [pollid] AND ckey = '[ckey]'") + var/datum/db_query/query_hasvoted = SSdbcore.NewQuery({" + SELECT id + FROM `[format_table_name(table)]` + WHERE pollid = :id AND ckey = :ckey + "}, list( + "id" = pollid, + "ckey" = ckey + )) if(!query_hasvoted.warn_execute()) qdel(query_hasvoted) return if(query_hasvoted.NextRow()) qdel(query_hasvoted) if(!silent) - to_chat(usr, "You've already replied to this poll.") + to_chat(usr, "You've already replied to this poll.", confidential = TRUE) return TRUE qdel(query_hasvoted) return FALSE @@ -376,24 +446,31 @@ /mob/dead/new_player/proc/vote_rig_check() if (usr != src) if (!usr || !src) - return 0 + return FALSE //we gots ourselfs a dirty cheater on our hands! log_game("[key_name(usr)] attempted to rig the vote by voting as [key]") message_admins("[key_name_admin(usr)] attempted to rig the vote by voting as [key]") to_chat(usr, "You don't seem to be [key].") to_chat(src, "Something went horribly wrong processing your vote. Please contact an administrator, they should have gotten a message about this") - return 0 - return 1 + return FALSE + return TRUE /mob/dead/new_player/proc/vote_valid_check(pollid, holder, type) - if (!SSdbcore.Connect()) + if(!SSdbcore.Connect()) to_chat(src, "Failed to establish database connection.") - return 0 + return pollid = text2num(pollid) if (!pollid || pollid < 0) return 0 //validate the poll is actually the right type of poll and its still active - var/datum/DBQuery/query_validate_poll = SSdbcore.NewQuery("SELECT id FROM [format_table_name("poll_question")] WHERE id = [pollid] AND Now() BETWEEN starttime AND endtime AND polltype = '[type]' [(holder ? "" : "AND adminonly = false")]") + var/datum/db_query/query_validate_poll = SSdbcore.NewQuery({" + SELECT id + FROM [format_table_name("poll_question")] + WHERE id = :id AND Now() BETWEEN starttime AND endtime AND polltype = :type [(holder ? "" : "AND adminonly = false")] + "}, list( + "id" = pollid, + "type" = type + )) if(!query_validate_poll.warn_execute()) qdel(query_validate_poll) return 0 @@ -403,88 +480,66 @@ qdel(query_validate_poll) return 1 +/** + * Processes vote form data and saves results to the database for an IRV type poll. + * + */ /mob/dead/new_player/proc/vote_on_irv_poll(pollid, list/votelist) - if (!SSdbcore.Connect()) + if(!SSdbcore.Connect()) to_chat(src, "Failed to establish database connection.") - return 0 - if (!vote_rig_check()) - return 0 - pollid = text2num(pollid) - if (!pollid || pollid < 0) - return 0 - if (!votelist || !istype(votelist) || !votelist.len) - return 0 - if (!client) - return 0 - //save these now so we can still process the vote if the client goes away while we process. - var/datum/admins/holder = client.holder - var/rank = "Player" - if (holder) - rank = holder.rank.name - var/ckey = client.ckey - var/address = client.address - - //validate the poll - if (!vote_valid_check(pollid, holder, POLLTYPE_IRV)) + return + if(IsAdminAdvancedProcCall()) + return + if(!vote_rig_check()) + return + if(!pollid) + return + // var/list/votelist = splittext(href_list["IRVdata"], ",") + if(!length(votelist)) + to_chat(src, "No ordering data found. Please try again or contact an administrator.") + var/admin_rank = "Player" + if(!QDELETED(client) && client.holder) + admin_rank = client.holder.rank.name + if (!vote_valid_check(pollid, client?.holder, POLLTYPE_IRV)) return 0 - //lets collect the options - var/datum/DBQuery/query_irv_id = SSdbcore.NewQuery("SELECT id FROM [format_table_name("poll_option")] WHERE pollid = [pollid]") - if(!query_irv_id.warn_execute()) - qdel(query_irv_id) - return 0 - var/list/optionlist = list() - while (query_irv_id.NextRow()) - optionlist += text2num(query_irv_id.item[1]) - qdel(query_irv_id) + var/list/special_columns = list( + "datetime" = "NOW()", + "ip" = "INET_ATON(?)", + ) - //validate their votes are actually in the list of options and actually numbers - var/list/numberedvotelist = list() - for (var/vote in votelist) - vote = text2num(vote) - numberedvotelist += vote - if (!vote) //this is fine because voteid starts at 1, so it will never be 0 - to_chat(src, "Error: Invalid (non-numeric) votes in the vote data.") - return 0 - if (!(vote in optionlist)) - to_chat(src, "Votes for choices that do not appear to be in the poll detected.") - return 0 - if (!numberedvotelist.len) - to_chat(src, "Invalid vote data") - return 0 - - //lets add the vote, first we generate an insert statement. - - var/sqlrowlist = "" - for (var/vote in numberedvotelist) - if (sqlrowlist != "") - sqlrowlist += ", " //a comma (,) at the start of the first row to insert will trigger a SQL error - sqlrowlist += "(Now(), [pollid], [vote], '[sanitizeSQL(ckey)]', INET_ATON('[sanitizeSQL(address)]'), '[sanitizeSQL(rank)]')" - - //now lets delete their old votes (if any) - var/datum/DBQuery/query_irv_del_old = SSdbcore.NewQuery("DELETE FROM [format_table_name("poll_vote")] WHERE pollid = [pollid] AND ckey = '[ckey]'") - if(!query_irv_del_old.warn_execute()) - qdel(query_irv_del_old) - return 0 - qdel(query_irv_del_old) - - //now to add the new ones. - var/datum/DBQuery/query_irv_vote = SSdbcore.NewQuery("INSERT INTO [format_table_name("poll_vote")] (datetime, pollid, optionid, ckey, ip, adminrank) VALUES [sqlrowlist]") - if(!query_irv_vote.warn_execute()) - qdel(query_irv_vote) - return 0 - qdel(query_irv_vote) - if(!QDELETED(src)) - src << browse(null,"window=playerpoll") - return 1 + var/sql_votes = list() + for(var/o in votelist) + var/voteid = text2num(o) + if(!voteid) + continue + sql_votes += list(list( + "pollid" = pollid, + "optionid" = voteid, + "ckey" = ckey, + "ip" = client.address, + "adminrank" = admin_rank + )) + //IRV results are calculated based on id order, we delete all of a user's votes to avoid potential errors caused by revoting and option editing + var/datum/db_query/query_delete_irv_votes = SSdbcore.NewQuery({" + UPDATE [format_table_name("poll_vote")] SET deleted = 1 WHERE pollid = :pollid AND ckey = :ckey + "}, list("pollid" = pollid, "ckey" = ckey)) + if(!query_delete_irv_votes.warn_execute()) + qdel(query_delete_irv_votes) + return + qdel(query_delete_irv_votes) + SSdbcore.MassInsert(format_table_name("poll_vote"), sql_votes, special_columns = special_columns) + return TRUE /mob/dead/new_player/proc/vote_on_poll(pollid, optionid) - if (!SSdbcore.Connect()) + if(!SSdbcore.Connect()) to_chat(src, "Failed to establish database connection.") - return 0 - if (!vote_rig_check()) - return 0 + return + if(!vote_rig_check()) + return + if(IsAdminAdvancedProcCall()) + return if(!pollid || !optionid) return //validate the poll @@ -493,10 +548,19 @@ var/voted = poll_check_voted(pollid) if(isnull(voted) || voted) //Failed or already voted. return - var/adminrank = sanitizeSQL(poll_rank()) + var/adminrank = poll_rank() if(!adminrank) return - var/datum/DBQuery/query_option_vote = SSdbcore.NewQuery("INSERT INTO [format_table_name("poll_vote")] (datetime, pollid, optionid, ckey, ip, adminrank) VALUES (Now(), [pollid], [optionid], '[ckey]', INET_ATON('[client.address]'), '[adminrank]')") + var/datum/db_query/query_option_vote = SSdbcore.NewQuery({" + INSERT INTO [format_table_name("poll_vote")] (datetime, pollid, optionid, ckey, ip, adminrank) + VALUES (Now(), :pollid, :optionid, :ckey, INET_ATON(:address), :adminrank) + "}, list( + "pollid" = pollid, + "optionid" = optionid, + "ckey" = ckey, + "address" = client.address, + "adminrank" = adminrank + )) if(!query_option_vote.warn_execute()) qdel(query_option_vote) return @@ -506,11 +570,13 @@ return 1 /mob/dead/new_player/proc/log_text_poll_reply(pollid, replytext) - if (!SSdbcore.Connect()) + if(!SSdbcore.Connect()) to_chat(src, "Failed to establish database connection.") - return 0 - if (!vote_rig_check()) - return 0 + return + if(!vote_rig_check()) + return + if(IsAdminAdvancedProcCall()) + return if(!pollid) return //validate the poll @@ -522,18 +588,23 @@ var/voted = poll_check_voted(pollid, text = TRUE, silent = TRUE) if(isnull(voted)) return - var/adminrank = sanitizeSQL(poll_rank()) + var/adminrank = poll_rank() if(!adminrank) return - replytext = sanitizeSQL(replytext) if(!(length(replytext) > 0) || !(length(replytext) <= 8000)) to_chat(usr, "The text you entered was invalid or too long. Please correct the text and submit again.") return - var/datum/DBQuery/query_text_vote + var/datum/db_query/query_text_vote if(!voted) - query_text_vote = SSdbcore.NewQuery("INSERT INTO [format_table_name("poll_textreply")] (datetime ,pollid ,ckey ,ip ,replytext ,adminrank) VALUES (Now(), [pollid], '[ckey]', INET_ATON('[client.address]'), '[replytext]', '[adminrank]')") + query_text_vote = SSdbcore.NewQuery({" + INSERT INTO [format_table_name("poll_textreply")] (datetime, pollid, ckey, ip, replytext, adminrank) + VALUES (Now(), :pollid, :ckey, INET_ATON(:address), :replytext, :adminrank) + "}, list("pollid" = pollid, "ckey" = ckey, "address" = client.address, "replytext" = replytext, "adminrank" = adminrank)) else - query_text_vote = SSdbcore.NewQuery("UPDATE [format_table_name("poll_textreply")] SET datetime = Now(), ip = INET_ATON('[client.address]'), replytext = '[replytext]' WHERE pollid = '[pollid]' AND ckey = '[ckey]'") + query_text_vote = SSdbcore.NewQuery({" + UPDATE [format_table_name("poll_textreply")] + SET datetime = Now(), ip = INET_ATON(:address), replytext = :replytext WHERE pollid = :pollid AND ckey = :ckey + "}, list("address" = client.address, "replytext" = replytext, "pollid" = pollid, "ckey" = ckey)) if(!query_text_vote.warn_execute()) qdel(query_text_vote) return @@ -543,17 +614,26 @@ return 1 /mob/dead/new_player/proc/vote_on_numval_poll(pollid, optionid, rating) - if (!SSdbcore.Connect()) + if(!SSdbcore.Connect()) to_chat(src, "Failed to establish database connection.") - return 0 - if (!vote_rig_check()) - return 0 + return + if(!vote_rig_check()) + return + if(IsAdminAdvancedProcCall()) + return if(!pollid || !optionid || !rating) return //validate the poll if (!vote_valid_check(pollid, client.holder, POLLTYPE_RATING)) return 0 - var/datum/DBQuery/query_numval_hasvoted = SSdbcore.NewQuery("SELECT id FROM [format_table_name("poll_vote")] WHERE optionid = [optionid] AND ckey = '[ckey]'") + var/datum/db_query/query_numval_hasvoted = SSdbcore.NewQuery({" + SELECT id + FROM [format_table_name("poll_vote")] + WHERE optionid = :id AND ckey = :ckey + "}, list( + "id" = optionid, + "ckey" = ckey + )) if(!query_numval_hasvoted.warn_execute()) qdel(query_numval_hasvoted) return @@ -565,8 +645,10 @@ var/adminrank = "Player" if(client.holder) adminrank = client.holder.rank.name - adminrank = sanitizeSQL(adminrank) - var/datum/DBQuery/query_numval_vote = SSdbcore.NewQuery("INSERT INTO [format_table_name("poll_vote")] (datetime ,pollid ,optionid ,ckey ,ip ,adminrank, rating) VALUES (Now(), [pollid], [optionid], '[ckey]', INET_ATON('[client.address]'), '[adminrank]', [(isnull(rating)) ? "null" : rating])") + var/datum/db_query/query_numval_vote = SSdbcore.NewQuery({" + INSERT INTO [format_table_name("poll_vote")] (datetime ,pollid ,optionid ,ckey ,ip ,adminrank, rating) + VALUES (Now(), :pollid, :optionid, :ckey, INET_ATON(:address), :adminrank, :rating) + "}, list("pollid" = pollid, "optionid" = optionid, "ckey" = ckey, "address" = client.address, "adminrank" = adminrank, "rating" = isnull(rating) ? "null" : rating)) if(!query_numval_vote.warn_execute()) qdel(query_numval_vote) return @@ -575,46 +657,59 @@ usr << browse(null,"window=playerpoll") return 1 +/** + * Processes vote form data and saves results to the database for a multiple choice type poll. + * + */ /mob/dead/new_player/proc/vote_on_multi_poll(pollid, optionid) - if (!SSdbcore.Connect()) + if(!SSdbcore.Connect()) to_chat(src, "Failed to establish database connection.") - return 0 - if (!vote_rig_check()) - return 0 - if(!pollid || !optionid) - return 1 + return + if(!vote_rig_check()) + return + if(IsAdminAdvancedProcCall()) + return //validate the poll - if (!vote_valid_check(pollid, client.holder, POLLTYPE_MULTI)) - return 0 - var/datum/DBQuery/query_multi_choicelen = SSdbcore.NewQuery("SELECT multiplechoiceoptions FROM [format_table_name("poll_question")] WHERE id = [pollid]") - if(!query_multi_choicelen.warn_execute()) - qdel(query_multi_choicelen) - return 1 - var/i - if(query_multi_choicelen.NextRow()) - i = text2num(query_multi_choicelen.item[1]) - qdel(query_multi_choicelen) - var/datum/DBQuery/query_multi_hasvoted = SSdbcore.NewQuery("SELECT id FROM [format_table_name("poll_vote")] WHERE pollid = [pollid] AND ckey = '[ckey]'") - if(!query_multi_hasvoted.warn_execute()) - qdel(query_multi_hasvoted) - return 1 - while(i) - if(query_multi_hasvoted.NextRow()) - i-- - else - break - qdel(query_multi_hasvoted) - if(!i) - return 2 - var/adminrank = "Player" - if(!QDELETED(client) && client.holder) - adminrank = client.holder.rank.name - adminrank = sanitizeSQL(adminrank) - var/datum/DBQuery/query_multi_vote = SSdbcore.NewQuery("INSERT INTO [format_table_name("poll_vote")] (datetime, pollid, optionid, ckey, ip, adminrank) VALUES (Now(), [pollid], [optionid], '[ckey]', INET_ATON('[client.address]'), '[adminrank]')") - if(!query_multi_vote.warn_execute()) - qdel(query_multi_vote) - return 1 - qdel(query_multi_vote) - if(!QDELETED(usr)) - usr << browse(null,"window=playerpoll") - return 0 + if(!vote_valid_check(pollid, client.holder, POLLTYPE_MULTI)) + return + if(!pollid || !optionid) + return + // if(length(href_list) > 2) + // href_list.Cut(1,3) //first two values aren't options + // else + // to_chat(src, "No options were selected.") + + var/special_columns = list( + "datetime" = "NOW()", + "ip" = "INET_ATON(?)", + ) + + var/sql_votes = list() + // var/vote_count = 0 + // for(var/h in href_list) + // if(vote_count == poll.options_allowed) + // to_chat(src, "Allowed option count exceeded, only the first [poll.options_allowed] selected options have been saved.") + // break + // vote_count++ + // var/datum/poll_option/option = locate(h) in poll.options + var/admin_rank = "Player" + if(!QDELETED(client) && client?.holder) + admin_rank = client.holder.rank.name + sql_votes += list(list( + "pollid" = pollid, + "optionid" = optionid, + "ckey" = ckey, + "ip" = client.address, + "adminrank" = admin_rank + )) + /*with revoting and poll editing possible there can be an edge case where a poll is changed to allow less multiple choice options than a user has already voted on + rather than trying to calculate which options should be updated and which deleted, we just delete all of a user's votes and re-insert as needed*/ + var/datum/db_query/query_delete_multi_votes = SSdbcore.NewQuery({" + UPDATE [format_table_name("poll_vote")] SET deleted = 1 WHERE pollid = :pollid AND ckey = :ckey + "}, list("pollid" = pollid, "ckey" = ckey)) + if(!query_delete_multi_votes.warn_execute()) + qdel(query_delete_multi_votes) + return + qdel(query_delete_multi_votes) + SSdbcore.MassInsert(format_table_name("poll_vote"), sql_votes, special_columns = special_columns) + return TRUE diff --git a/code/modules/mob/dead/new_player/sprite_accessories/body_markings.dm b/code/modules/mob/dead/new_player/sprite_accessories/body_markings.dm index 4e5f010964..bc6c4899dd 100644 --- a/code/modules/mob/dead/new_player/sprite_accessories/body_markings.dm +++ b/code/modules/mob/dead/new_player/sprite_accessories/body_markings.dm @@ -341,4 +341,4 @@ /datum/sprite_accessory/insect_fluff/witchwing name = "Witch Wing" - icon_state = "witchwing" \ No newline at end of file + icon_state = "witchwing" diff --git a/code/modules/mob/dead/new_player/sprite_accessories/caps.dm b/code/modules/mob/dead/new_player/sprite_accessories/caps.dm index 822fcf960d..d974672490 100644 --- a/code/modules/mob/dead/new_player/sprite_accessories/caps.dm +++ b/code/modules/mob/dead/new_player/sprite_accessories/caps.dm @@ -1,4 +1,4 @@ -datum/sprite_accessory/caps +/datum/sprite_accessory/caps icon = 'icons/mob/mutant_bodyparts.dmi' color_src = HAIR relevant_layers = list(BODY_ADJ_LAYER) diff --git a/code/modules/mob/dead/new_player/sprite_accessories/hair_head.dm b/code/modules/mob/dead/new_player/sprite_accessories/hair_head.dm index 55ab18c20c..0c22a85886 100644 --- a/code/modules/mob/dead/new_player/sprite_accessories/hair_head.dm +++ b/code/modules/mob/dead/new_player/sprite_accessories/hair_head.dm @@ -13,6 +13,10 @@ name = "Bald" icon_state = "bald" +/datum/sprite_accessory/hair/adhara + name = "Adhara" + icon_state = "hair_adhara" + /datum/sprite_accessory/hair/afro name = "Afro" icon_state = "hair_afro" @@ -29,6 +33,14 @@ name = "Ahoge" icon_state = "hair_antenna" +/datum/sprite_accessory/hair/amazon + name = "Amazon" + icon_state = "hair_amazon" + +/datum/sprite_accessory/hair/anita + name = "Anita" + icon_state = "hair_anita" + /datum/sprite_accessory/hair/balding name = "Balding Hair" icon_state = "hair_e" @@ -61,6 +73,18 @@ name = "Beehive 2" icon_state = "hair_beehive2" +/datum/sprite_accessory/hair/belenko + name = "Beleneko" + icon_state = "hair_belenko" + +/datum/sprite_accessory/hair/belenkotied + name = "Belenko (Tied)" + icon_state = "hair_belenkotied" + +/datum/sprite_accessory/hair/belle + name = "Belle" + icon_state = "hair_belle" + /datum/sprite_accessory/hair/bob name = "Bob Hair" icon_state = "hair_bob" @@ -309,6 +333,10 @@ name = "Flow Hair" icon_state = "hair_f" +/datum/sprite_accessory/hair/fluffy + name = "Fluffy" + icon_state = "hair_fluffy" + /datum/sprite_accessory/hair/fringetail name = "Fringe Tail" icon_state = "hair_fringetail" @@ -365,6 +393,10 @@ name = "Hitop" icon_state = "hair_hitop" +/datum/sprite_accessory/hair/inkling + name = "Inkling" + icon_state = "hair_inkling" + /datum/sprite_accessory/hair/jade name = "Jade" icon_state = "hair_jade" @@ -373,6 +405,10 @@ name = "Jensen" icon_state = "hair_jensen" +/datum/sprite_accessory/hair/jessica + name = "Jessica" + icon_state = "hair_jessica" + /datum/sprite_accessory/hair/joestar name = "Joestar" icon_state = "hair_joestar" @@ -389,6 +425,10 @@ name = "Kusanagi" icon_state = "hair_kusanagi" +/datum/sprite_accessory/hair/kleeia + name = "Kleeia" + icon_state = "hair_kleeia" + /datum/sprite_accessory/hair/long name = "Long Hair 1" icon_state = "hair_long" @@ -445,6 +485,10 @@ name = "Mohawk" icon_state = "hair_d" +/datum/sprite_accessory/hair/newyou + name = "New You" + icon_state = "hair_newyou" + /datum/sprite_accessory/hair/reversemohawk name = "Mohawk (Reverse)" icon_state = "hair_reversemohawk" @@ -575,6 +619,10 @@ name = "Ponytail (Side) 4" icon_state = "hair_sidetail4" +/datum/sprite_accessory/hair/sharptail + name = "Ponytail (Sharp)" + icon_state = "hair_sharptail" + /datum/sprite_accessory/hair/spikytail name = "Ponytail (Spiky)" icon_state = "hair_spikyponytail" @@ -679,6 +727,26 @@ name = "Skinhead" icon_state = "hair_skinhead" +/datum/sprite_accessory/hair/simple + name = "Simple" + icon_state = "hair_simple" + +/datum/sprite_accessory/hair/skrellvshort + name = "Skrell Replicant (Very Short)" + icon_state = "hair_skrellvshort" + +/datum/sprite_accessory/hair/skrellshort + name = "Skrell Replicant (Short)" + icon_state = "hair_skrellshort" + +/datum/sprite_accessory/hair/skrell + name = "Skrell Replicant (Average)" + icon_state = "hair_skrell" + +/datum/sprite_accessory/hair/skrelllong + name = "Skrell Replicant (Long)" + icon_state = "hair_skrelllong" + /datum/sprite_accessory/hair/sleaze name = "Sleaze" icon_state = "hair_sleaze" @@ -699,6 +767,10 @@ name = "Spiky 3" icon_state = "hair_spiky2" +/datum/sprite_accessory/hair/supernova + name = "Supernova" + icon_state = "hair_supernova" + /datum/sprite_accessory/hair/swept name = "Swept Back Hair" icon_state = "hair_swept" @@ -743,6 +815,10 @@ name = "Trimmed (Flat)" icon_state = "hair_trimflat" +/datum/sprite_accessory/hair/twincurls + name = "Twincurls" + icon_state = "hair_twincurls" + /datum/sprite_accessory/hair/twintails name = "Twintails" icon_state = "hair_twintail" @@ -787,6 +863,10 @@ name = "Very Short Over Eye (Alt)" icon_state = "hair_veryshortovereyealternate" +/datum/sprite_accessory/hair/vivi + name = "Vivi" + icon_state = "hair_vivi" + /datum/sprite_accessory/hair/volaju name = "Volaju" icon_state = "hair_volaju" diff --git a/code/modules/mob/dead/new_player/sprite_accessories/socks.dm b/code/modules/mob/dead/new_player/sprite_accessories/socks.dm index cd55a17e3c..78517b003e 100644 --- a/code/modules/mob/dead/new_player/sprite_accessories/socks.dm +++ b/code/modules/mob/dead/new_player/sprite_accessories/socks.dm @@ -11,6 +11,11 @@ // please make sure they're sorted alphabetically and categorized +/datum/sprite_accessory/underwear/socks/garter + name = "Garter" + icon_state = "garter" + has_color = TRUE + /datum/sprite_accessory/underwear/socks/socks_knee name = "Knee-high" icon_state = "socks_knee" @@ -83,6 +88,10 @@ name = "Pantyhose" icon_state = "pantyhose" +/datum/sprite_accessory/underwear/socks/pantyhose_ripped + name = "Pantyhose - Ripped" + icon_state = "pantyhose_ripped" + /datum/sprite_accessory/underwear/socks/socks_short name = "Short" icon_state = "socks_short" diff --git a/code/modules/mob/dead/new_player/sprite_accessories/tails.dm b/code/modules/mob/dead/new_player/sprite_accessories/tails.dm index 6135d42569..a964be3f8a 100644 --- a/code/modules/mob/dead/new_player/sprite_accessories/tails.dm +++ b/code/modules/mob/dead/new_player/sprite_accessories/tails.dm @@ -757,7 +757,7 @@ icon_state = "husky" matrixed_sections = MATRIX_RED_GREEN -datum/sprite_accessory/tails/mam_tails/insect +/datum/sprite_accessory/tails/mam_tails/insect name = "Insect" icon_state = "insect" matrixed_sections = MATRIX_RED diff --git a/code/modules/mob/dead/new_player/sprite_accessories/undershirt.dm b/code/modules/mob/dead/new_player/sprite_accessories/undershirt.dm index 1be02c207e..364485fe8a 100644 --- a/code/modules/mob/dead/new_player/sprite_accessories/undershirt.dm +++ b/code/modules/mob/dead/new_player/sprite_accessories/undershirt.dm @@ -343,6 +343,20 @@ icon_state = "fishnet_body" gender = FEMALE +/datum/sprite_accessory/underwear/top/shibari + name = "Shibari Ropes" + icon_state = "shibari" + gender = FEMALE + has_color = TRUE + covers_chest = FALSE + +/datum/sprite_accessory/underwear/top/shibari_sleeved + name = "Shibari Ropes - sleeves" + icon_state = "shibari_sleeves" + gender = FEMALE + has_color = TRUE + covers_chest = FALSE + /datum/sprite_accessory/underwear/top/swimsuit name = "Swimsuit Top" icon_state = "bra_swimming" diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm index dc53f9487f..783f55d12d 100644 --- a/code/modules/mob/dead/observer/observer.dm +++ b/code/modules/mob/dead/observer/observer.dm @@ -556,7 +556,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp var/list/dest = list() //List of possible destinations (mobs) var/target = null //Chosen target. - dest += getpois(mobs_only=1) //Fill list, prompt user with list + dest += getpois(mobs_only = TRUE) //Fill list, prompt user with list target = input("Please, select a player!", "Jump to Mob", null, null) as null|anything in dest if (!target)//Make sure we actually have a target @@ -893,7 +893,9 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp if (!eye_name) return - var/mob/mob_eye = creatures[eye_name] + do_observe(creatures[eye_name]) + +/mob/dead/observer/proc/do_observe(mob/mob_eye) //Istype so we filter out points of interest that are not mobs if(client && mob_eye && istype(mob_eye)) client.eye = mob_eye diff --git a/code/modules/mob/dead/observer/orbit.dm b/code/modules/mob/dead/observer/orbit.dm index 3aa5f8e302..26494dcb34 100644 --- a/code/modules/mob/dead/observer/orbit.dm +++ b/code/modules/mob/dead/observer/orbit.dm @@ -1,5 +1,6 @@ /datum/orbit_menu var/mob/dead/observer/owner + var/auto_observe = FALSE /datum/orbit_menu/New(mob/dead/observer/new_owner) if(!istype(new_owner)) @@ -10,6 +11,7 @@ return GLOB.observer_state /datum/orbit_menu/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) if (!ui) ui = new(user, src, "Orbit") ui.open() @@ -18,15 +20,35 @@ if (..()) return - if (action == "orbit") - var/ref = params["ref"] - var/atom/movable/poi = (locate(ref) in GLOB.mob_list) || (locate(ref) in GLOB.poi_list) - if (poi != null) + switch(action) + if ("orbit") + var/ref = params["ref"] + var/atom/movable/poi = (locate(ref) in GLOB.mob_list) || (locate(ref) in GLOB.poi_list) + if (poi == null) + . = TRUE + return owner.ManualFollow(poi) + owner.reset_perspective(null) + if (auto_observe) + owner.do_observe(poi) + . = TRUE + if ("refresh") + update_static_data(owner, ui) + . = TRUE + if ("toggle_observe") + auto_observe = !auto_observe + if (auto_observe && owner.orbit_target) + owner.do_observe(owner.orbit_target) + else + owner.reset_perspective(null) /datum/orbit_menu/ui_data(mob/user) var/list/data = list() + data["auto_observe"] = auto_observe + return data +/datum/orbit_menu/ui_static_data(mob/user) + var/list/data = list() var/list/alive = list() var/list/antagonists = list() var/list/dead = list() @@ -34,7 +56,7 @@ var/list/misc = list() var/list/npcs = list() - var/list/pois = getpois(skip_mindless = 1) + var/list/pois = getpois(skip_mindless = TRUE, specify_dead_role = FALSE) for (var/name in pois) var/list/serialized = list() serialized["name"] = name @@ -80,7 +102,7 @@ data["npcs"] = npcs return data - + /datum/orbit_menu/ui_assets() . = ..() || list() . += get_asset_datum(/datum/asset/simple/orbit) diff --git a/code/modules/mob/death.dm b/code/modules/mob/death.dm index 09336d2fea..476bddded3 100644 --- a/code/modules/mob/death.dm +++ b/code/modules/mob/death.dm @@ -1,6 +1,6 @@ //This is the proc for gibbing a mob. Cannot gib ghosts. //added different sort of gibs and animations. N -/mob/proc/gib() +/mob/proc/gib(no_brain, no_organs, no_bodyparts, datum/explosion/was_explosion) return //This is the proc for turning a mob into ash. Mostly a copy of gib code (above). diff --git a/code/modules/mob/inventory.dm b/code/modules/mob/inventory.dm index 9cf3f44cb8..3eaefebc56 100644 --- a/code/modules/mob/inventory.dm +++ b/code/modules/mob/inventory.dm @@ -298,20 +298,21 @@ return doUnEquip(I, force, drop_location(), FALSE) //for when the item will be immediately placed in a loc other than the ground -/mob/proc/transferItemToLoc(obj/item/I, newloc = null, force = FALSE) - return doUnEquip(I, force, newloc, FALSE) +/mob/proc/transferItemToLoc(obj/item/I, newloc = null, force = FALSE, silent = TRUE) + return doUnEquip(I, force, newloc, FALSE, silent = silent) //visibly unequips I but it is NOT MOVED AND REMAINS IN SRC //item MUST BE FORCEMOVE'D OR QDEL'D /mob/proc/temporarilyRemoveItemFromInventory(obj/item/I, force = FALSE, idrop = TRUE) - return doUnEquip(I, force, null, TRUE, idrop) + return doUnEquip(I, force, null, TRUE, idrop, silent = TRUE) //DO NOT CALL THIS PROC //use one of the above 3 helper procs //you may override it, but do not modify the args -/mob/proc/doUnEquip(obj/item/I, force, newloc, no_move, invdrop = TRUE) //Force overrides TRAIT_NODROP for things like wizarditis and admin undress. +/mob/proc/doUnEquip(obj/item/I, force, newloc, no_move, invdrop = TRUE, silent = FALSE) //Force overrides TRAIT_NODROP for things like wizarditis and admin undress. //Use no_move if the item is just gonna be immediately moved afterward //Invdrop is used to prevent stuff in pockets dropping. only set to false if it's going to immediately be replaced + PROTECTED_PROC(TRUE) if(!I) //If there's nothing to drop, the drop is automatically succesfull. If(unEquip) should generally be used to check for TRAIT_NODROP. return TRUE diff --git a/code/modules/mob/living/carbon/alien/alien.dm b/code/modules/mob/living/carbon/alien/alien.dm index e7ad67d240..f5970d9da2 100644 --- a/code/modules/mob/living/carbon/alien/alien.dm +++ b/code/modules/mob/living/carbon/alien/alien.dm @@ -4,7 +4,6 @@ gender = FEMALE //All xenos are girls!! dna = null faction = list(ROLE_ALIEN) - ventcrawler = VENTCRAWLER_ALWAYS sight = SEE_MOBS see_in_dark = 4 verb_say = "hisses" @@ -12,6 +11,10 @@ bubble_icon = "alien" type_of_meat = /obj/item/reagent_containers/food/snacks/meat/slab/xeno + /// Whether they can ventcrawl; this is set individually for 'humanoid' and 'royal' types + /// 'royal' types (Praetorian, Queen) cannot ventcrawl + var/can_ventcrawl + /// How much brute damage without armor piercing they do against mobs in melee var/meleeSlashHumanPower = 20 /// How much power they have for DefaultCombatKnockdown when attacking humans @@ -19,7 +22,6 @@ /// How much brute damage they do to simple animals var/meleeSlashSAPower = 35 - var/obj/item/card/id/wear_id = null // Fix for station bounced radios -- Skie var/has_fine_manipulation = 0 var/move_delay_add = 0 // movement delay to add @@ -40,6 +42,9 @@ create_internal_organs() + if(can_ventcrawl) + AddElement(/datum/element/ventcrawling, given_tier = VENTCRAWLER_ALWAYS) + . = ..() /mob/living/carbon/alien/create_internal_organs() diff --git a/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm b/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm index d0addbab21..0d2a1c0c59 100644 --- a/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm +++ b/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm @@ -20,6 +20,8 @@ bodyparts = list(/obj/item/bodypart/chest/alien, /obj/item/bodypart/head/alien, /obj/item/bodypart/l_arm/alien, /obj/item/bodypart/r_arm/alien, /obj/item/bodypart/r_leg/alien, /obj/item/bodypart/l_leg/alien) + can_ventcrawl = TRUE + //This is fine right now, if we're adding organ specific damage this needs to be updated /mob/living/carbon/alien/humanoid/Initialize() diff --git a/code/modules/mob/living/carbon/alien/humanoid/queen.dm b/code/modules/mob/living/carbon/alien/humanoid/queen.dm index 91a61efea6..bcc83f14f9 100644 --- a/code/modules/mob/living/carbon/alien/humanoid/queen.dm +++ b/code/modules/mob/living/carbon/alien/humanoid/queen.dm @@ -2,7 +2,6 @@ //Common stuffs for Praetorian and Queen icon = 'icons/mob/alienqueen.dmi' status_flags = 0 - ventcrawler = VENTCRAWLER_NONE //pull over that ass too fat unique_name = 0 pixel_x = -16 bubble_icon = "alienroyal" @@ -10,6 +9,7 @@ layer = LARGE_MOB_LAYER //above most mobs, but below speechbubbles pressure_resistance = 200 //Because big, stompy xenos should not be blown around like paper. butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab/xeno = 20, /obj/item/stack/sheet/animalhide/xeno = 3) + can_ventcrawl = FALSE meleeKnockdownPower = 125 meleeSlashHumanPower = 30 diff --git a/code/modules/mob/living/carbon/alien/larva/inventory.dm b/code/modules/mob/living/carbon/alien/larva/inventory.dm index 8cfbf21f75..b07bd180f6 100644 --- a/code/modules/mob/living/carbon/alien/larva/inventory.dm +++ b/code/modules/mob/living/carbon/alien/larva/inventory.dm @@ -1,3 +1,3 @@ //can't unequip since it can't equip anything -/mob/living/carbon/alien/larva/doUnEquip(obj/item/W) +/mob/living/carbon/alien/larva/doUnEquip(obj/item/W, silent = FALSE) return diff --git a/code/modules/mob/living/carbon/alien/larva/larva.dm b/code/modules/mob/living/carbon/alien/larva/larva.dm index 8f0ef2b384..77c9a8c579 100644 --- a/code/modules/mob/living/carbon/alien/larva/larva.dm +++ b/code/modules/mob/living/carbon/alien/larva/larva.dm @@ -11,6 +11,8 @@ maxHealth = 25 health = 25 + can_ventcrawl = TRUE + var/amount_grown = 0 var/max_grown = 100 var/time_of_birth diff --git a/code/modules/mob/living/carbon/alien/special/facehugger.dm b/code/modules/mob/living/carbon/alien/special/facehugger.dm index 5392e7b2d5..ca2946fd8a 100644 --- a/code/modules/mob/living/carbon/alien/special/facehugger.dm +++ b/code/modules/mob/living/carbon/alien/special/facehugger.dm @@ -91,8 +91,8 @@ Attach(M) /obj/item/clothing/mask/facehugger/Crossed(atom/target) + . = ..() HasProximity(target) - return /obj/item/clothing/mask/facehugger/on_found(mob/finder) if(stat == CONSCIOUS) diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index 45a0d82d2c..f143f6302d 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -434,6 +434,9 @@ legcuffed.forceMove(drop_location()) legcuffed = null I.dropped(src) + if(istype(I, /obj/item/restraints/legcuffs)) + var/obj/item/restraints/legcuffs/lgcf = I + lgcf.on_removed() update_inv_legcuffed() return else @@ -1229,3 +1232,25 @@ */ /mob/living/carbon/proc/get_biological_state() return BIO_FLESH_BONE + +/mob/living/carbon/altattackby(obj/item/W, mob/living/carbon/user, params) + if(user.incapacitated() || !user.Adjacent(src)) + return FALSE + if(W && user.a_intent == INTENT_HELP && W.can_give()) + user.give() + return TRUE + +/mob/living/carbon/verb/give_verb() + set src in oview(1) + set category = "IC" + set name = "Give" + + if(usr.incapacitated() || !usr.Adjacent(src)) + return + + if(!usr.get_active_held_item()) // Let me know if this has any problems -Yota + return + var/obj/item/I = usr.get_active_held_item() + var/mob/living/carbon/C = usr + if(I.can_give()) + C.give() diff --git a/code/modules/mob/living/carbon/carbon_defense.dm b/code/modules/mob/living/carbon/carbon_defense.dm index 7a16d5c352..b2c14c30fe 100644 --- a/code/modules/mob/living/carbon/carbon_defense.dm +++ b/code/modules/mob/living/carbon/carbon_defense.dm @@ -304,10 +304,16 @@ emote("wag") else if(check_zone(M.zone_selected) == BODY_ZONE_R_ARM || check_zone(M.zone_selected) == BODY_ZONE_L_ARM) - M.visible_message( \ - "[M] shakes [src]'s hand.", \ - "You shake [src]'s hand.", target = src, - target_message = "[M] shakes your hand.") + if((pulling == M) && (grab_state == GRAB_PASSIVE)) + M.visible_message( \ + "[M] squeezes [src]'s hand.", \ + "You squeeze [src]'s hand.", target = src, + target_message = "[M] squeezes your hand.") + else + M.visible_message( \ + "[M] shakes [src]'s hand.", \ + "You shake [src]'s hand.", target = src, + target_message = "[M] shakes your hand.") else M.visible_message("[M] hugs [src] to make [p_them()] feel better!", \ diff --git a/code/modules/mob/living/carbon/death.dm b/code/modules/mob/living/carbon/death.dm index ee2f945b65..528848fb14 100644 --- a/code/modules/mob/living/carbon/death.dm +++ b/code/modules/mob/living/carbon/death.dm @@ -17,7 +17,7 @@ if(SSticker.mode) SSticker.mode.check_win() //Calls the rounds wincheck, mainly for wizard, malf, and changeling now -/mob/living/carbon/gib(no_brain, no_organs, no_bodyparts) +/mob/living/carbon/gib(no_brain, no_organs, no_bodyparts, datum/explosion/was_explosion) var/atom/Tsec = drop_location() for(var/mob/M in src) if(M in stomach_contents) @@ -27,7 +27,7 @@ "You burst out of [src]!") ..() -/mob/living/carbon/spill_organs(no_brain, no_organs, no_bodyparts) +/mob/living/carbon/spill_organs(no_brain, no_organs, no_bodyparts, datum/explosion/was_explosion) var/atom/Tsec = drop_location() if(!no_bodyparts) if(no_organs)//so the organs don't get transfered inside the bodyparts we'll drop. @@ -41,6 +41,8 @@ qdel(O) //so the brain isn't transfered to the head when the head drops. continue if(!(O.organ_flags & ORGAN_NO_DISMEMBERMENT) && check_zone(O.zone) == BODY_ZONE_CHEST) + if(was_explosion) + LAZYADD(O.acted_explosions, was_explosion.explosion_id) O.Remove() O.forceMove(Tsec) O.throw_at(get_edge_target_turf(src,pick(GLOB.alldirs)),rand(1,3),5) @@ -50,13 +52,16 @@ if(I.organ_flags & ORGAN_NO_DISMEMBERMENT || (no_brain && istype(I, /obj/item/organ/brain)) || (no_organs && !istype(I, /obj/item/organ/brain))) qdel(I) continue + if(was_explosion) + LAZYADD(I.acted_explosions, was_explosion.explosion_id) I.Remove() I.forceMove(Tsec) I.throw_at(get_edge_target_turf(src,pick(GLOB.alldirs)),rand(1,3),5) - -/mob/living/carbon/spread_bodyparts() +/mob/living/carbon/spread_bodyparts(no_brain, no_organs, datum/explosion/was_explosion) for(var/X in bodyparts) var/obj/item/bodypart/BP = X + if(was_explosion) + LAZYADD(BP.acted_explosions, was_explosion.explosion_id) BP.drop_limb() BP.throw_at(get_edge_target_turf(src,pick(GLOB.alldirs)),rand(1,3),5) diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index 03816555b3..8660e115a6 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -3,7 +3,7 @@ real_name = "Unknown" icon = 'icons/mob/human.dmi' icon_state = "caucasian_m" - appearance_flags = KEEP_TOGETHER|TILE_BOUND|PIXEL_SCALE + SET_APPEARANCE_FLAGS(KEEP_TOGETHER|TILE_BOUND|PIXEL_SCALE) /mob/living/carbon/human/Initialize() add_verb(src, /mob/living/proc/mob_sleep) @@ -281,11 +281,13 @@ dropItemToGround(pocket_item) if(!usr.can_hold_items() || !usr.put_in_hands(pocket_item)) pocket_item.forceMove(drop_location()) + log_combat(usr, src, "pickpocketed of item: [pocket_item]") else if(place_item) if(place_item.mob_can_equip(src, usr, pocket_id, FALSE, TRUE)) usr.temporarilyRemoveItemFromInventory(place_item, TRUE) equip_to_slot(place_item, pocket_id, TRUE) + log_combat(usr, src, "placed item [place_item] onto") //do nothing otherwise // Update strip window @@ -293,8 +295,9 @@ show_inv(usr) else // Display a warning if the user mocks up - if (!strip_silence) + if(!strip_silence) to_chat(src, "You feel your [pocket_side] pocket being fumbled with!") + log_combat(usr, src, "failed to [pocket_item ? "pickpocket item [pocket_item] from" : "place item [place_item] onto "]") if(usr.canUseTopic(src, BE_CLOSE, NO_DEXTERY, null, FALSE)) // separate from first canusetopic @@ -938,43 +941,43 @@ admin_ticket_log(src, msg) /mob/living/carbon/human/MouseDrop_T(mob/living/target, mob/living/user) - if(pulling == target && grab_state >= GRAB_AGGRESSIVE && stat == CONSCIOUS) + var/GS_needed = istype(target, /mob/living/silicon/pai)? GRAB_PASSIVE : GRAB_AGGRESSIVE + if(pulling == target && grab_state >= GS_needed && stat == CONSCIOUS) //If they dragged themselves and we're currently aggressively grabbing them try to piggyback if(user == target && can_piggyback(target)) piggyback(target) return //If you dragged them to you and you're aggressively grabbing try to fireman carry them - else if(user != target) + else if(user == src) if(user.a_intent == INTENT_GRAB) fireman_carry(target) return . = ..() //src is the user that will be carrying, target is the mob to be carried -/mob/living/carbon/human/proc/can_piggyback(mob/living/carbon/target) - return (istype(target) && target.stat == CONSCIOUS) +/mob/living/carbon/human/proc/can_piggyback(mob/living/target) + return (iscarbon(target) || ispAI(target)) && target.stat == CONSCIOUS /mob/living/carbon/human/proc/can_be_firemanned(mob/living/carbon/target) - return (ishuman(target) && !CHECK_MOBILITY(target, MOBILITY_STAND)) + return (ishuman(target) && !CHECK_MOBILITY(target, MOBILITY_STAND)) || ispAI(target) /mob/living/carbon/human/proc/fireman_carry(mob/living/carbon/target) var/carrydelay = 50 //if you have latex you are faster at grabbing var/skills_space = "" //cobby told me to do this if(HAS_TRAIT(src, TRAIT_QUICKER_CARRY)) carrydelay = 30 - skills_space = "expertly" + skills_space = "expertly " else if(HAS_TRAIT(src, TRAIT_QUICK_CARRY)) carrydelay = 40 - skills_space = "quickly" + skills_space = "quickly " if(can_be_firemanned(target) && !incapacitated(FALSE, TRUE)) - visible_message("[src] starts [skills_space] lifting [target] onto their back..", + visible_message("[src] starts [skills_space]lifting [target] onto their back..", //Joe Medic starts quickly/expertly lifting Grey Tider onto their back.. - "[carrydelay < 35 ? "Using your gloves' nanochips, you" : "You"] [skills_space] start to lift [target] onto your back[carrydelay == 40 ? ", while assisted by the nanochips in your gloves.." : "..."]") + "[carrydelay < 35 ? "Using your gloves' nanochips, you" : "You"] [skills_space]start to lift [target] onto your back[carrydelay == 40 ? ", while assisted by the nanochips in your gloves.." : "..."]") //(Using your gloves' nanochips, you/You) ( /quickly/expertly) start to lift Grey Tider onto your back(, while assisted by the nanochips in your gloves../...) if(do_after(src, carrydelay, TRUE, target)) //Second check to make sure they're still valid to be carried if(can_be_firemanned(target) && !incapacitated(FALSE, TRUE)) - target.set_resting(FALSE, TRUE) buckle_mob(target, TRUE, TRUE, 90, 1, 0, TRUE) return visible_message("[src] fails to fireman carry [target]!") @@ -992,13 +995,13 @@ if(target.incapacitated(FALSE, TRUE) || incapacitated(FALSE, TRUE)) target.visible_message("[target] can't hang onto [src]!") return - buckle_mob(target, TRUE, TRUE, FALSE, 1, 2, FALSE) + buckle_mob(target, TRUE, TRUE, 0, 1, 2, FALSE) else visible_message("[target] fails to climb onto [src]!") else to_chat(target, "You can't piggyback ride [src] right now!") -/mob/living/carbon/human/buckle_mob(mob/living/target, force = FALSE, check_loc = TRUE, lying_buckle = FALSE, hands_needed = 0, target_hands_needed = 0, fireman = FALSE) +/mob/living/carbon/human/buckle_mob(mob/living/target, force = FALSE, check_loc = TRUE, lying_buckle = 0, hands_needed = 0, target_hands_needed = 0, fireman = FALSE) if(!force)//humans are only meant to be ridden through piggybacking and special cases return if(!is_type_in_typecache(target, can_ride_typecache)) @@ -1010,6 +1013,9 @@ riding_datum.ride_check_rider_restrained = TRUE if(buckled_mobs && ((target in buckled_mobs) || (buckled_mobs.len >= max_buckled_mobs)) || buckled) return + if(istype(target, /mob/living/silicon/pai)) + hands_needed = 1 + target_hands_needed = 0 var/equipped_hands_self var/equipped_hands_target if(hands_needed) @@ -1028,7 +1034,7 @@ return stop_pulling() - riding_datum.handle_vehicle_layer() + riding_datum.handle_vehicle_layer(dir) riding_datum.fireman_carrying = fireman . = ..(target, force, check_loc) diff --git a/code/modules/mob/living/carbon/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm index a82151cc1d..9f80b5a933 100644 --- a/code/modules/mob/living/carbon/human/human_defines.dm +++ b/code/modules/mob/living/carbon/human/human_defines.dm @@ -71,10 +71,11 @@ var/list/datum/bioware = list() var/creamed = FALSE //to use with creampie overlays - var/static/list/can_ride_typecache = typecacheof(list(/mob/living/carbon/human, /mob/living/simple_animal/slime, /mob/living/simple_animal/parrot)) + var/static/list/can_ride_typecache = typecacheof(list(/mob/living/carbon/human, /mob/living/simple_animal/slime, /mob/living/simple_animal/parrot, /mob/living/silicon/pai)) var/lastpuke = 0 var/account_id var/last_fire_update + var/hardcore_survival_score = 0 /// Unarmed parry data for human /datum/block_parry_data/unarmed/human @@ -95,9 +96,10 @@ parry_efficiency_considered_successful = 0.01 parry_efficiency_to_counterattack = 0.01 parry_max_attacks = 3 - parry_cooldown = 30 - parry_failed_stagger_duration = 0 - parry_failed_clickcd_duration = 0.4 + parry_cooldown = 3 SECONDS + parry_failed_cooldown_duration = 1.5 SECONDS + parry_failed_stagger_duration = 1 SECONDS + parry_failed_clickcd_duration = 0.4 SECONDS parry_data = list( // yeah it's snowflake "UNARMED_PARRY_STAGGER" = 3 SECONDS, @@ -135,14 +137,14 @@ parry_imperfect_falloff_percent = 20 parry_efficiency_perfect = 100 parry_efficiency_perfect_override = list( - ATTACK_TYPE_PROJECTILE_TEXT = 60, + TEXT_ATTACK_TYPE_PROJECTILE = 60, ) parry_efficiency_considered_successful = 0.01 parry_efficiency_to_counterattack = 0.01 parry_max_attacks = INFINITY parry_failed_cooldown_duration = 1.5 SECONDS - parry_failed_stagger_duration = 0 + parry_failed_stagger_duration = 1 SECONDS parry_cooldown = 0 parry_failed_clickcd_duration = 0.8 diff --git a/code/modules/mob/living/carbon/human/human_movement.dm b/code/modules/mob/living/carbon/human/human_movement.dm index 440400f889..1619d0f6de 100644 --- a/code/modules/mob/living/carbon/human/human_movement.dm +++ b/code/modules/mob/living/carbon/human/human_movement.dm @@ -9,11 +9,6 @@ return return considering -/mob/living/carbon/human/movement_delay() - . = ..() - if (m_intent == MOVE_INTENT_WALK && HAS_TRAIT(src, TRAIT_SPEEDY_STEP)) - . -= 1.5 - /mob/living/carbon/human/slip(knockdown_amount, obj/O, lube) if(HAS_TRAIT(src, TRAIT_NOSLIPALL)) return 0 diff --git a/code/modules/mob/living/carbon/human/inventory.dm b/code/modules/mob/living/carbon/human/inventory.dm index 523369d10a..c4d023da58 100644 --- a/code/modules/mob/living/carbon/human/inventory.dm +++ b/code/modules/mob/living/carbon/human/inventory.dm @@ -164,7 +164,7 @@ var/obj/item/thing = sloties . += thing?.slowdown -/mob/living/carbon/human/doUnEquip(obj/item/I, force, newloc, no_move, invdrop = TRUE) +/mob/living/carbon/human/doUnEquip(obj/item/I, force, newloc, no_move, invdrop = TRUE, silent = FALSE) var/index = get_held_index_of_item(I) . = ..() //See mob.dm for an explanation on this and some rage about people copypasting instead of calling ..() like they should. if(!. || !I) diff --git a/code/modules/mob/living/carbon/human/species.dm b/code/modules/mob/living/carbon/human/species.dm index 94d7e84c6b..986fc38cd3 100644 --- a/code/modules/mob/living/carbon/human/species.dm +++ b/code/modules/mob/living/carbon/human/species.dm @@ -3,16 +3,30 @@ GLOBAL_LIST_EMPTY(roundstart_races) GLOBAL_LIST_EMPTY(roundstart_race_names) +/** + * # species datum + * + * Datum that handles different species in the game. + * + * This datum handles species in the game, such as lizardpeople, mothmen, zombies, skeletons, etc. + * It is used in [carbon humans][mob/living/carbon/human] to determine various things about them, like their food preferences, if they have biological genders, their damage resistances, and more. + * + */ /datum/species - var/id // if the game needs to manually check your race to do something not included in a proc here, it will use this - var/limbs_id //this is used if you want to use a different species limb sprites. Mainly used for angels as they look like humans. - var/name // this is the fluff name. these will be left generic (such as 'Lizardperson' for the lizard race) so servers can change them to whatever - var/default_color = "#FFFFFF" // if alien colors are disabled, this is the color that will be used by that race + ///If the game needs to manually check your race to do something not included in a proc here, it will use this. + var/id + //This is used if you want to use a different species' limb sprites. + var/limbs_id + ///This is the fluff name. They are displayed on health analyzers and in the character setup menu. Leave them generic for other servers to customize. + var/name + // Default color. If mutant colors are disabled, this is the color that will be used by that race. + var/default_color = "#FFF" - var/sexes = 1 // whether or not the race has sexual characteristics. at the moment this is only 0 for skeletons and shadows + ///Whether or not the race has sexual characteristics (biological genders). At the moment this is only FALSE for skeletons and shadows + var/sexes = TRUE var/has_field_of_vision = TRUE - //Species Icon Drawing Offsets - Pixel X, Pixel Y, Aka X = Horizontal and Y = Vertical, from bottom left corner + ///Clothing offsets. If a species has a different body than other species, you can offset clothing so they look less weird. var/list/offset_features = list( OFFSET_UNIFORM = list(0,0), OFFSET_ID = list(0,0), @@ -34,71 +48,141 @@ GLOBAL_LIST_EMPTY(roundstart_race_names) OFFSET_MUTPARTS = list(0,0) ) - var/hair_color // this allows races to have specific hair colors... if null, it uses the H's hair/facial hair colors. if "mutcolor", it uses the H's mutant_color - var/hair_alpha = 255 // the alpha used by the hair. 255 is completely solid, 0 is transparent. - var/use_skintones = NO_SKINTONES // does it use skintones or not? (spoiler alert this is only used by humans) - var/exotic_blood = "" // If your race wants to bleed something other than bog standard blood, change this to reagent id. - var/exotic_bloodtype = "" //If your race uses a non standard bloodtype (A+, O-, AB-, etc) - var/exotic_blood_color = BLOOD_COLOR_HUMAN //assume human as the default blood colour, override this default by species subtypes + ///This allows races to have specific hair colors. If null, it uses the H's hair/facial hair colors. If "mutcolor", it uses the H's mutant_color. If "fixedmutcolor", it uses fixedmutcolor + var/hair_color + ///The alpha used by the hair. 255 is completely solid, 0 is invisible. + var/hair_alpha = 255 + + ///Does the species use skintones or not? As of now only used by humans. + var/use_skintones = FALSE + ///If your race bleeds something other than bog standard blood, change this to reagent id. For example, ethereals bleed liquid electricity. + var/exotic_blood = "" + ///If your race uses a non standard bloodtype (A+, O-, AB-, etc). For example, lizards have L type blood. + var/exotic_bloodtype = "" + /// Assume human as the default blood colour, override this default by species subtypes + var/exotic_blood_color = BLOOD_COLOR_HUMAN + ///What the species drops when gibbed by a gibber machine. var/meat = /obj/item/reagent_containers/food/snacks/meat/slab/human //What the species drops on gibbing var/list/gib_types = list(/obj/effect/gibspawner/human, /obj/effect/gibspawner/human/bodypartless) + ///What skin the species drops when gibbed by a gibber machine. var/skinned_type + ///Bitfield for food types that the species likes, giving them a mood boost. Lizards like meat, for example. var/liked_food = NONE + ///Bitfield for food types that the species dislikes, giving them disgust. Humans hate raw food, for example. var/disliked_food = GROSS + ///Bitfield for food types that the species absolutely hates, giving them even more disgust than disliked food. Meat is "toxic" to moths, for example. var/toxic_food = TOXIC - var/list/no_equip = list() // slots the race can't equip stuff to - var/nojumpsuit = 0 // this is sorta... weird. it basically lets you equip stuff that usually needs jumpsuits without one, like belts and pockets and ids + ///Inventory slots the race can't equip stuff to. Golems cannot wear jumpsuits, for example. + var/list/no_equip = list() + /// Allows the species to equip items that normally require a jumpsuit without having one equipped. Used by golems. + var/nojumpsuit = FALSE var/blacklisted = 0 //Flag to exclude from green slime core species. var/dangerous_existence //A flag for transformation spells that tells them "hey if you turn a person into one of these without preperation, they'll probably die!" - var/say_mod = "says" // affects the speech message + ///Affects the speech message, for example: Motharula flutters, "My speech message is flutters!" + var/say_mod = "says" + ///What languages this species can understand and say. Use a [language holder datum][/datum/language_holder] in this var. var/species_language_holder = /datum/language_holder - var/list/mutant_bodyparts = list() // Visible CURRENT bodyparts that are unique to a species. Changes to this list for non-species specific bodyparts (ie cat ears and tails) should be assigned at organ level if possible. Layer hiding is handled by handle_mutant_bodyparts() below. - var/list/mutant_organs = list() //Internal organs that are unique to this race. - var/speedmod = 0 // this affects the race's speed. positive numbers make it move slower, negative numbers make it move faster - var/armor = 0 // overall defense for the race... or less defense, if it's negative. - var/attack_type = BRUTE // the type of damage unarmed attacks from this species do - var/brutemod = 1 // multiplier for brute damage - var/burnmod = 1 // multiplier for burn damage - var/coldmod = 1 // multiplier for cold damage - var/heatmod = 1 // multiplier for heat damage - var/stunmod = 1 // multiplier for stun duration - var/punchdamagelow = 1 //lowest possible punch damage. if this is set to 0, punches will always miss - var/punchdamagehigh = 10 //highest possible punch damage - var/punchstunthreshold = 10 //damage at which punches from this race will stun //yes it should be to the attacked race but it's not useful that way even if it's logical + /** + * Visible CURRENT bodyparts that are unique to a species. + * DO NOT USE THIS AS A LIST OF ALL POSSIBLE BODYPARTS AS IT WILL FUCK + * SHIT UP! Changes to this list for non-species specific bodyparts (ie + * cat ears and tails) should be assigned at organ level if possible. + * Assoc values are defaults for given bodyparts, also modified by aforementioned organs. + * They also allow for faster '[]' list access versus 'in'. Other than that, they are useless right now. + * Layer hiding is handled by [/datum/species/proc/handle_mutant_bodyparts] below. + */ + var/list/mutant_bodyparts = list() + ///Internal organs that are unique to this race, like a tail. + var/list/mutant_organs = list() + ///Multiplier for the race's speed. Positive numbers make it move slower, negative numbers make it move faster. + var/speedmod = 0 + ///Percentage modifier for overall defense of the race, or less defense, if it's negative. + var/armor = 0 + ///multiplier for brute damage + var/brutemod = 1 + ///multiplier for burn damage + var/burnmod = 1 + ///multiplier for damage from cold temperature + var/coldmod = 1 + ///multiplier for damage from hot temperature + var/heatmod = 1 + ///multiplier for stun durations + var/stunmod = 1 + ///multiplier for money paid at payday + var/payday_modifier = 1 + ///Type of damage attack does. Ethereals attack with burn damage for example. + var/attack_type = BRUTE // multiplier for stun duration + ///Lowest possible punch damage this species can give. If this is set to 0, punches will always miss. + var/punchdamagelow = 1 + ///Highest possible punch damage this species can give. + var/punchdamagehigh = 10 + ///Damage at which punches from this race will stun + var/punchstunthreshold = 10 //yes it should be to the attacked race but it's not useful that way even if it's logical var/punchwoundbonus = 0 // additional wound bonus. generally zero. - var/siemens_coeff = 1 //base electrocution coefficient - var/damage_overlay_type = "human" //what kind of damage overlays (if any) appear on our species when wounded? - var/fixed_mut_color = "" //to use MUTCOLOR with a fixed color that's independent of dna.feature["mcolor"] + ///Base electrocution coefficient. Basically a multiplier for damage from electrocutions. + var/siemens_coeff = 1 + ///What kind of damage overlays (if any) appear on our species when wounded? If this is "", does not add an overlay. + var/damage_overlay_type = "human" + ///To use MUTCOLOR with a fixed color that's independent of the mcolor feature in DNA. + var/fixed_mut_color = "" + ///Special mutation that can be found in the genepool exclusively in this species. Dont leave empty or changing species will be a headache var/inert_mutation = DWARFISM - var/list/special_step_sounds //Sounds to override barefeet walkng - var/grab_sound //Special sound for grabbing - var/datum/outfit/outfit_important_for_life // A path to an outfit that is important for species life e.g. plasmaman outfit + ///Sounds to override barefeet walking + var/list/special_step_sounds + ///Special sound for grabbing + var/grab_sound + /// A path to an outfit that is important for species life e.g. plasmaman outfit + var/datum/outfit/outfit_important_for_life - // species-only traits. Can be found in DNA.dm + ///Species-only traits. Can be found in [code/__DEFINES/DNA.dm] var/list/species_traits = list(HAS_FLESH,HAS_BONE) //by default they can scar and have bones/flesh unless set to something else - // generic traits tied to having the species - var/list/inherent_traits = list() + ///Generic traits tied to having the species. + var/list/inherent_traits = list() //list(TRAIT_ADVANCEDTOOLUSER) + /// List of biotypes the mob belongs to. Used by diseases. var/inherent_biotypes = MOB_ORGANIC|MOB_HUMANOID - var/attack_verb = "punch" // punch-specific attack verb + var/list/blacklisted_quirks = list() // Quirks that will be removed upon gaining this species, to be defined by species + var/list/removed_quirks = list() // Quirks that got removed due to being blacklisted, and will be restored when on_species_loss() is called + + ///Punch-specific attack verb. + var/attack_verb = "punch" + /// var/sound/attack_sound = 'sound/weapons/punch1.ogg' var/sound/miss_sound = 'sound/weapons/punchmiss.ogg' var/list/mob/living/ignored_by = list() // list of mobs that will ignore this species //Breathing! var/obj/item/organ/lungs/mutantlungs = null + ///What gas does this species breathe? Used by suffocation screen alerts, most of actual gas breathing is handled by mutantlungs. See [life.dm][code/modules/mob/living/carbon/human/life.dm] var/breathid = "o2" + //Do NOT remove by setting to null. use OR make a RESPECTIVE TRAIT (removing stomach? add the NOSTOMACH trait to your species) + //why does it work this way? because traits also disable the downsides of not having an organ, removing organs but not having the trait will make your species die + + ///Replaces default brain with a different organ var/obj/item/organ/brain/mutant_brain = /obj/item/organ/brain + ///Replaces default heart with a different organ var/obj/item/organ/heart/mutant_heart = /obj/item/organ/heart + ///Replaces default lungs with a different organ + // var/obj/item/organ/lungs/mutantlungs = /obj/item/organ/lungs + ///Replaces default eyes with a different organ var/obj/item/organ/eyes/mutanteyes = /obj/item/organ/eyes + ///Replaces default ears with a different organ var/obj/item/organ/ears/mutantears = /obj/item/organ/ears - var/obj/item/mutanthands + ///Replaces default tongue with a different organ var/obj/item/organ/tongue/mutanttongue = /obj/item/organ/tongue + ///Replaces default liver with a different organ + var/obj/item/organ/liver/mutantliver = /obj/item/organ/liver + ///Replaces default stomach with a different organ + var/obj/item/organ/stomach/mutantstomach = /obj/item/organ/stomach + ///Replaces default appendix with a different organ. + var/obj/item/organ/appendix/mutantappendix = /obj/item/organ/appendix + ///Forces an item into this species' hands. Only an honorary mutantthing because this is not an organ and not loaded in the same way, you've been warned to do your research. + var/obj/item/mutanthands + + /// CIT SPECIFIC Mutant tail var/obj/item/organ/tail/mutanttail = null - var/obj/item/organ/liver/mutantliver - var/obj/item/organ/stomach/mutantstomach var/override_float = FALSE //Citadel snowflake @@ -123,6 +207,9 @@ GLOBAL_LIST_EMPTY(roundstart_race_names) //the icon state of the eyes this species has var/eye_type = "normal" + ///For custom overrides for species ass images + var/icon/ass_image + /////////// // PROCS // /////////// @@ -138,6 +225,12 @@ GLOBAL_LIST_EMPTY(roundstart_race_names) //update our mutant bodyparts to include unlocked ones mutant_bodyparts += GLOB.unlocked_mutant_parts +/** + * Generates species available to choose in character setup at roundstart + * + * This proc generates which species are available to pick from in character setup. + * If there are no available roundstart species, defaults to human. + */ /proc/generate_selectable_species(clear = FALSE) if(clear) GLOB.roundstart_races = list() @@ -151,11 +244,26 @@ GLOBAL_LIST_EMPTY(roundstart_race_names) if(!GLOB.roundstart_races.len) GLOB.roundstart_races += "human" +/** + * Checks if a species is eligible to be picked at roundstart. + * + * Checks the config to see if this species is allowed to be picked in the character setup menu. + * Used by [/proc/generate_selectable_species]. + */ /datum/species/proc/check_roundstart_eligible() if(id in (CONFIG_GET(keyed_list/roundstart_races))) return TRUE return FALSE +/** + * Generates a random name for a carbon. + * + * This generates a random unique name based on a human's species and gender. + * Arguments: + * * gender - The gender that the name should adhere to. Use MALE for male names, use anything else for female names. + * * unique - If true, ensures that this new name is not a duplicate of anyone else's name currently on the station. + * * lastname - Does this species' naming system adhere to the last name system? Set to false if it doesn't. + */ /datum/species/proc/random_name(gender,unique,lastname) if(unique) return random_unique_name(gender) @@ -173,7 +281,13 @@ GLOBAL_LIST_EMPTY(roundstart_race_names) return randname -//Called when cloning, copies some vars that should be kept +/** + * Copies some vars and properties over that should be kept when creating a copy of this species. + * + * Used by slimepeople to copy themselves, and by the DNA datum to hardset DNA to a species + * Arguments: + * * old_species - The species that the carbon used to be before copying + */ /datum/species/proc/copy_properties_from(datum/species/old_species) mutant_bodyparts["limbs_id"] = old_species.mutant_bodyparts["limbs_id"] eye_type = old_species.eye_type @@ -186,7 +300,18 @@ GLOBAL_LIST_EMPTY(roundstart_race_names) // return 0 //It returns false when it runs the proc so they don't get jobs from the global list. return 1 //It returns 1 to say they are a-okay to continue. -//Will regenerate missing organs +/** + * Corrects organs in a carbon, removing ones it doesn't need and adding ones it does. + * + * Takes all organ slots, removes organs a species should not have, adds organs a species should have. + * can use replace_current to refresh all organs, creating an entirely new set. + * + * Arguments: + * * C - carbon, the owner of the species datum AKA whoever we're regenerating organs in + * * old_species - datum, used when regenerate organs is called in a switching species to remove old mutant organs. + * * replace_current - boolean, forces all old organs to get deleted whether or not they pass the species' ability to keep that organ + * * excluded_zones - list, add zone defines to block organs inside of the zones from getting handled. see headless mutation for an example + */ /datum/species/proc/regenerate_organs(mob/living/carbon/C,datum/species/old_species,replace_current=TRUE) var/obj/item/organ/brain/brain = C.getorganslot(ORGAN_SLOT_BRAIN) var/obj/item/organ/heart/heart = C.getorganslot(ORGAN_SLOT_HEART) @@ -302,6 +427,16 @@ GLOBAL_LIST_EMPTY(roundstart_race_names) var/obj/item/organ/I = new path() I.Insert(C) +/** + * Proc called when a carbon becomes this species. + * + * This sets up and adds/changes/removes things, qualities, abilities, and traits so that the transformation is as smooth and bugfree as possible. + * Produces a [COMSIG_SPECIES_GAIN] signal. + * Arguments: + * * C - Carbon, this is whoever became the new species. + * * old_species - The species that the carbon used to be before becoming this race, used for regenerating organs. + * * pref_load - Preferences to be loaded from character setup, loads in preferred mutant things like bodyparts, digilegs, skin color, etc. + */ /datum/species/proc/on_species_gain(mob/living/carbon/C, datum/species/old_species, pref_load) // Drop the items the new species can't wear for(var/slot_id in no_equip) @@ -342,6 +477,9 @@ GLOBAL_LIST_EMPTY(roundstart_race_names) for(var/X in inherent_traits) ADD_TRAIT(C, X, SPECIES_TRAIT) + //lets remove those conflicting quirks + remove_blacklisted_quirks(C) + if(TRAIT_VIRUSIMMUNE in inherent_traits) for(var/datum/disease/A in C.diseases) A.cure(FALSE) @@ -395,6 +533,9 @@ GLOBAL_LIST_EMPTY(roundstart_race_names) for(var/X in inherent_traits) REMOVE_TRAIT(C, X, SPECIES_TRAIT) + // lets restore the quirks that got removed when gaining this species + restore_quirks(C) + C.remove_movespeed_modifier(/datum/movespeed_modifier/species) if(mutant_bodyparts["meat_type"]) @@ -424,6 +565,26 @@ GLOBAL_LIST_EMPTY(roundstart_race_names) SEND_SIGNAL(C, COMSIG_SPECIES_LOSS, src) +// shamelessly inspired by antag_datum.remove_blacklisted_quirks() +/datum/species/proc/remove_blacklisted_quirks(mob/living/carbon/C) + var/mob/living/L = C.mind?.current + if(istype(L)) + var/list/my_quirks = L.client?.prefs.all_quirks.Copy() + SSquirks.filter_quirks(my_quirks, blacklisted_quirks) + for(var/q in L.roundstart_quirks) + var/datum/quirk/Q = q + if(!(SSquirks.quirk_name_by_path(Q.type) in my_quirks)) + L.remove_quirk(Q.type) + removed_quirks += Q.type + +// restore any quirks that we removed +/datum/species/proc/restore_quirks(mob/living/carbon/C) + var/mob/living/L = C.mind?.current + if(istype(L)) + for(var/q in removed_quirks) + L.add_quirk(q) + + /datum/species/proc/handle_hair(mob/living/carbon/human/H, forced_colour) H.remove_overlay(HAIR_LAYER) var/obj/item/bodypart/head/HD = H.get_bodypart(BODY_ZONE_HEAD) @@ -1425,8 +1586,6 @@ GLOBAL_LIST_EMPTY(roundstart_race_names) var/damage = rand(user.dna.species.punchdamagelow, user.dna.species.punchdamagehigh) var/punchwoundbonus = user.dna.species.punchwoundbonus - var/puncherstam = user.getStaminaLoss() - var/puncherbrute = user.getBruteLoss() var/punchedstam = target.getStaminaLoss() var/punchedbrute = target.getBruteLoss() @@ -1434,7 +1593,7 @@ GLOBAL_LIST_EMPTY(roundstart_race_names) if(!SEND_SIGNAL(target, COMSIG_COMBAT_MODE_CHECK, COMBAT_MODE_INACTIVE)) damage *= 1.2 if(!CHECK_MOBILITY(user, MOBILITY_STAND)) - damage *= 0.8 + damage *= 0.65 if(SEND_SIGNAL(user, COMSIG_COMBAT_MODE_CHECK, COMBAT_MODE_INACTIVE)) damage *= 0.8 //END OF CITADEL CHANGES @@ -1446,19 +1605,7 @@ GLOBAL_LIST_EMPTY(roundstart_race_names) if(!affecting) //Maybe the bodypart is missing? Or things just went wrong.. affecting = target.get_bodypart(BODY_ZONE_CHEST) //target chest instead, as failsafe. Or hugbox? You decide. - var/miss_chance = 100//calculate the odds that a punch misses entirely. considers stamina and brute damage of the puncher. punches miss by default to prevent weird cases - if(attackchain_flags & ATTACK_IS_PARRY_COUNTERATTACK) - miss_chance = 0 - else - if(user.dna.species.punchdamagelow) - if(atk_verb == ATTACK_EFFECT_KICK) //kicks never miss (provided your species deals more than 0 damage) - miss_chance = 0 - else if(HAS_TRAIT(user, TRAIT_PUGILIST)) //pugilists, being good at Punching People, also never miss - miss_chance = 0 - else - miss_chance = min(10 + max(puncherstam * 0.5, puncherbrute * 0.5), 100) //probability of miss has a base of 10, and modified based on half brute total. Capped at max 100 to prevent weirdness in prob() - - if(!damage || !affecting || prob(miss_chance))//future-proofing for species that have 0 damage/weird cases where no zone is targeted + if(!damage || !affecting)//future-proofing for species that have 0 damage/weird cases where no zone is targeted playsound(target.loc, user.dna.species.miss_sound, 25, TRUE, -1) target.visible_message("[user]'s [atk_verb] misses [target]!", \ "You avoid [user]'s [atk_verb]!", "You hear a swoosh!", null, COMBAT_MESSAGE_RANGE, null, \ @@ -1468,9 +1615,6 @@ GLOBAL_LIST_EMPTY(roundstart_race_names) var/armor_block = target.run_armor_check(affecting, "melee") - if(HAS_TRAIT(user, TRAIT_MAULER)) // maulers get 15 armorpierce because if you're going to punch someone you might as well do a good job of it - armor_block = target.run_armor_check(affecting, "melee", armour_penetration = 15) // lot of good that sec jumpsuit did you - playsound(target.loc, user.dna.species.attack_sound, 25, 1, -1) target.visible_message("[user] [atk_verb]ed [target]!", \ "[user] [atk_verb]ed you!", null, COMBAT_MESSAGE_RANGE, null, \ @@ -1487,9 +1631,8 @@ GLOBAL_LIST_EMPTY(roundstart_race_names) target.apply_damage(damage*1.5, attack_type, affecting, armor_block, wound_bonus = punchwoundbonus) target.apply_damage(damage*0.5, STAMINA, affecting, armor_block) log_combat(user, target, "kicked") - else if(HAS_TRAIT(user, TRAIT_MAULER)) // mauler punches deal 1.1x raw damage + 1.3x stam damage, and have some armor pierce - target.apply_damage(damage*1.1, attack_type, affecting, armor_block, wound_bonus = punchwoundbonus) - target.apply_damage(damage*1.3, STAMINA, affecting, armor_block) + else if(HAS_TRAIT(user, TRAIT_MAULER)) // mauler punches deal 1.2x raw damage but nstam + target.apply_damage(damage*1.2, attack_type, affecting, armor_block, wound_bonus = punchwoundbonus) log_combat(user, target, "punched (mauler)") else //other attacks deal full raw damage + 2x in stamina damage target.apply_damage(damage, attack_type, affecting, armor_block, wound_bonus = punchwoundbonus) @@ -1561,9 +1704,9 @@ GLOBAL_LIST_EMPTY(roundstart_race_names) if(!user.UseStaminaBuffer(3, warn = TRUE)) return FALSE user.do_attack_animation(target, ATTACK_EFFECT_ASS_SLAP) - target.adjust_arousal(20,maso = TRUE) + target.adjust_arousal(20,"masochism", maso = TRUE) if (ishuman(target) && HAS_TRAIT(target, TRAIT_MASO) && target.has_dna() && prob(10)) - target.mob_climax(forced_climax=TRUE) + target.mob_climax(forced_climax=TRUE, cause = "masochism") if (!HAS_TRAIT(target, TRAIT_PERMABONER)) stop_wagging_tail(target) playsound(target.loc, 'sound/weapons/slap.ogg', 50, 1, -1) @@ -1796,12 +1939,10 @@ GLOBAL_LIST_EMPTY(roundstart_race_names) "[M] attempted to touch you!", target = M, \ target_message = "You attempted to touch [H]!") return TRUE + if(M == H) + althelp(M, H, attacker_style) + return TRUE switch(M.a_intent) - if(INTENT_HELP) - if(M == H) - althelp(M, H, attacker_style) - return TRUE - return FALSE if(INTENT_DISARM) altdisarm(M, H, attacker_style) return TRUE @@ -1947,7 +2088,7 @@ GLOBAL_LIST_EMPTY(roundstart_race_names) if(BP.receive_damage(damage_amount, 0, wound_bonus = wound_bonus, bare_wound_bonus = bare_wound_bonus, sharpness = sharpness)) H.update_damage_overlays() if(HAS_TRAIT(H, TRAIT_MASO) && prob(damage_amount)) - H.mob_climax(forced_climax=TRUE) + H.mob_climax(forced_climax=TRUE, cause = "masochism") else//no bodypart, we deal damage with a more general method. H.adjustBruteLoss(damage_amount) @@ -1987,6 +2128,8 @@ GLOBAL_LIST_EMPTY(roundstart_race_names) H.show_message("The radiation beam dissipates harmlessly through your body.") if(/obj/item/projectile/energy/florayield) H.show_message("The radiation beam dissipates harmlessly through your body.") + if(/obj/item/projectile/energy/florarevolution) + H.show_message("The radiation beam dissipates harmlessly through your body.") /datum/species/proc/bullet_act(obj/item/projectile/P, mob/living/carbon/human/H) // called before a projectile hit diff --git a/code/modules/mob/living/carbon/human/species_types/abductor.dm b/code/modules/mob/living/carbon/human/species_types/abductor.dm index 004ba267e8..253dac6f2a 100644 --- a/code/modules/mob/living/carbon/human/species_types/abductor.dm +++ b/code/modules/mob/living/carbon/human/species_types/abductor.dm @@ -7,6 +7,7 @@ inherent_traits = list(TRAIT_VIRUSIMMUNE,TRAIT_CHUNKYFINGERS,TRAIT_NOHUNGER,TRAIT_NOBREATH) mutanttongue = /obj/item/organ/tongue/abductor species_category = SPECIES_CATEGORY_ALIEN + ass_image = 'icons/ass/assgrey.png' /datum/species/abductor/on_species_gain(mob/living/carbon/C, datum/species/old_species) . = ..() diff --git a/code/modules/mob/living/carbon/human/species_types/dwarves.dm b/code/modules/mob/living/carbon/human/species_types/dwarves.dm index 7c35ade4f2..e0a9bcaa36 100644 --- a/code/modules/mob/living/carbon/human/species_types/dwarves.dm +++ b/code/modules/mob/living/carbon/human/species_types/dwarves.dm @@ -30,10 +30,7 @@ GLOBAL_LIST_INIT(dwarf_last, world.file2list("strings/names/dwarf_last.txt")) // /datum/species/dwarf/on_species_gain(mob/living/carbon/C, datum/species/old_species) . = ..() - var/dwarf_hair = pick("Beard (Dwarf)", "Beard (Very Long)", "Beard (Long)") //beard roullette var/mob/living/carbon/human/H = C - H.facial_hair_style = dwarf_hair - H.update_hair() H.AddElement(/datum/element/dwarfism, COMSIG_SPECIES_LOSS, src) RegisterSignal(C, COMSIG_MOB_SAY, .proc/handle_speech) //We register handle_speech is being used. diff --git a/code/modules/mob/living/carbon/human/species_types/felinid.dm b/code/modules/mob/living/carbon/human/species_types/felinid.dm index 3fcefbe2cc..6327375bb5 100644 --- a/code/modules/mob/living/carbon/human/species_types/felinid.dm +++ b/code/modules/mob/living/carbon/human/species_types/felinid.dm @@ -12,6 +12,7 @@ tail_type = "mam_tail" wagging_type = "mam_waggingtail" species_category = SPECIES_CATEGORY_FURRY + ass_image = 'icons/ass/asscat.png' /datum/species/human/felinid/on_species_gain(mob/living/carbon/C, datum/species/old_species, pref_load) if(ishuman(C)) diff --git a/code/modules/mob/living/carbon/human/species_types/golems.dm b/code/modules/mob/living/carbon/human/species_types/golems.dm index ff0287d7b9..75aff774ce 100644 --- a/code/modules/mob/living/carbon/human/species_types/golems.dm +++ b/code/modules/mob/living/carbon/human/species_types/golems.dm @@ -801,11 +801,11 @@ /datum/species/golem/plastic/on_species_gain(mob/living/carbon/C, datum/species/old_species) . = ..() - C.ventcrawler = VENTCRAWLER_NUDE + C.AddElement(/datum/element/ventcrawling, given_tier = VENTCRAWLER_ALWAYS) /datum/species/golem/plastic/on_species_loss(mob/living/carbon/C) . = ..() - C.ventcrawler = initial(C.ventcrawler) + C.RemoveElement(/datum/element/ventcrawling, given_tier = VENTCRAWLER_ALWAYS) /datum/species/golem/bronze name = "Bronze Golem" diff --git a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm index dc2269e19b..7a15fd2e5c 100644 --- a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm @@ -27,6 +27,7 @@ tail_type = "mam_tail" wagging_type = "mam_waggingtail" species_category = SPECIES_CATEGORY_JELLY + ass_image = 'icons/ass/assslime.png' /obj/item/organ/brain/jelly name = "slime nucleus" diff --git a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm index 4d0f88754f..6a9845079f 100644 --- a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm @@ -30,6 +30,8 @@ wagging_type = "waggingtail_lizard" species_category = SPECIES_CATEGORY_LIZARD + ass_image = 'icons/ass/asslizard.png' + /datum/species/lizard/random_name(gender,unique,lastname) if(unique) return random_unique_lizard_name(gender) diff --git a/code/modules/mob/living/carbon/human/species_types/plasmamen.dm b/code/modules/mob/living/carbon/human/species_types/plasmamen.dm index 12a520b463..e368489c57 100644 --- a/code/modules/mob/living/carbon/human/species_types/plasmamen.dm +++ b/code/modules/mob/living/carbon/human/species_types/plasmamen.dm @@ -24,6 +24,8 @@ species_category = SPECIES_CATEGORY_SKELETON + ass_image = 'icons/ass/assplasma.png' + /datum/species/plasmaman/spec_life(mob/living/carbon/human/H) var/datum/gas_mixture/environment = H.loc.return_air() var/atmos_sealed = FALSE diff --git a/code/modules/mob/living/carbon/human/species_types/podpeople.dm b/code/modules/mob/living/carbon/human/species_types/podpeople.dm index c25c5efb6c..697c3db35e 100644 --- a/code/modules/mob/living/carbon/human/species_types/podpeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/podpeople.dm @@ -64,6 +64,11 @@ H.show_message("The radiation beam singes you!") if(/obj/item/projectile/energy/florayield) H.adjust_nutrition(30, NUTRITION_LEVEL_FULL) + if(/obj/item/projectile/energy/florarevolution) + H.show_message("The radiation beam leaves you feeling disoriented!") + H.Dizzy(15) + H.emote("flip") + H.emote("spin") /datum/species/pod/pseudo_weak name = "Anthromorphic Plant" diff --git a/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm b/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm index 33a2a09b70..329fa5ee65 100644 --- a/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm @@ -187,12 +187,8 @@ AM.emp_act(50) if(iscyborg(AM)) var/mob/living/silicon/robot/borg = AM - if(borg.lamp_intensity) - borg.update_headlamp(TRUE, INFINITY) - to_chat(borg, "Your headlamp is fried! You'll need a human to help replace it.") - for(var/obj/item/assembly/flash/cyborg/F in borg.held_items) - if(!F.crit_fail) - F.burn_out() + if(borg.lamp_enabled) + borg.smash_headlamp() else for(var/obj/item/O in AM) if(O.light_range && O.light_power) diff --git a/code/modules/mob/living/carbon/human/species_types/vampire.dm b/code/modules/mob/living/carbon/human/species_types/vampire.dm index 558fdc6594..89b3d603ea 100644 --- a/code/modules/mob/living/carbon/human/species_types/vampire.dm +++ b/code/modules/mob/living/carbon/human/species_types/vampire.dm @@ -58,7 +58,7 @@ to_chat(C, "You ran out of blood!") C.dust() var/area/A = get_area(C) - if(istype(A, /area/chapel)) + if(istype(A, /area/chapel) && C.mind?.assigned_role != "Chaplain") to_chat(C, "You don't belong here!") C.adjustFireLoss(5) C.adjust_fire_stacks(6) @@ -169,7 +169,7 @@ if(istype(H, /mob/living/simple_animal)) var/mob/living/simple_animal/SA = H if((human_caster.blood_volume <= (BLOOD_VOLUME_BAD*human_caster.blood_ratio)) || (ventcrawl_nude_only && length(human_caster.get_equipped_items(include_pockets = TRUE)))) - SA.ventcrawler = FALSE + SA.RemoveElement(/datum/element/ventcrawling, given_tier = VENTCRAWLER_ALWAYS) if(transfer_name) H.name = human_caster.name diff --git a/code/modules/mob/living/carbon/human/species_types/zombies.dm b/code/modules/mob/living/carbon/human/species_types/zombies.dm index 385dd94f04..57a11481d7 100644 --- a/code/modules/mob/living/carbon/human/species_types/zombies.dm +++ b/code/modules/mob/living/carbon/human/species_types/zombies.dm @@ -37,6 +37,7 @@ armor = 20 // 120 damage to KO a zombie, which kills it speedmod = 1.6 // they're very slow mutanteyes = /obj/item/organ/eyes/night_vision/zombie + blacklisted_quirks = list(/datum/quirk/nonviolent) var/heal_rate = 1 var/regen_cooldown = 0 diff --git a/code/modules/mob/living/carbon/inventory.dm b/code/modules/mob/living/carbon/inventory.dm index 96dab9e95d..0447de6064 100644 --- a/code/modules/mob/living/carbon/inventory.dm +++ b/code/modules/mob/living/carbon/inventory.dm @@ -104,7 +104,7 @@ return not_handled -/mob/living/carbon/doUnEquip(obj/item/I, force, newloc, no_move, invdrop = TRUE) +/mob/living/carbon/doUnEquip(obj/item/I, force, newloc, no_move, invdrop = TRUE, silent = FALSE) . = ..() //Sets the default return value to what the parent returns. if(!. || !I) //We don't want to set anything to null if the parent returned 0. return @@ -159,3 +159,57 @@ /mob/living/carbon/proc/get_holding_bodypart_of_item(obj/item/I) var/index = get_held_index_of_item(I) return index && hand_bodyparts[index] + +/** + * Proc called when giving an item to another player + * + * This handles creating an alert and adding an overlay to it + */ +/mob/living/carbon/proc/give(target) + var/obj/item/receiving = get_active_held_item() + if(!receiving) + to_chat(src, "You're not holding anything to give!") + return + visible_message("[src] is offering [receiving]", \ + "You offer [receiving]", null, 2) + var/mob/living/carbon/targets = list() + if(!target) + for(var/mob/living/carbon/C in orange(1, src)) + if(!CanReach(C)) + return + targets += C + else + targets += target + if(!targets) + return + for(var/mob/living/carbon/C in targets) + var/obj/screen/alert/give/G = C.throw_alert("[src]", /obj/screen/alert/give) + if(!G) + return + G.setup(C, src, receiving) + +/** + * Proc called when the player clicks the give alert + * + * Handles checking if the player taking the item has open slots and is in range of the giver + * Also deals with the actual transferring of the item to the players hands + * Arguments: + * * giver - The person giving the original item + * * I - The item being given by the giver + */ +/mob/living/carbon/proc/take(mob/living/carbon/giver, obj/item/I) + clear_alert("[giver]") + if(get_dist(src, giver) > 1) + to_chat(src, "[giver] is out of range! ") + return + if(!I || giver.get_active_held_item() != I) + to_chat(src, "[giver] is no longer holding the item they were offering! ") + return + if(!get_empty_held_indexes()) + to_chat(src, "You have no empty hands!") + return + if(!giver.temporarilyRemoveItemFromInventory(I)) + visible_message("[src] tries to hand over [I] but it's stuck to them....", \ + " You make a fool of yourself trying to give away an item stuck to your hands") + return + put_in_hands(I) diff --git a/code/modules/mob/living/carbon/life.dm b/code/modules/mob/living/carbon/life.dm index e0fab331a6..9885efd7c7 100644 --- a/code/modules/mob/living/carbon/life.dm +++ b/code/modules/mob/living/carbon/life.dm @@ -594,10 +594,18 @@ GLOBAL_LIST_INIT(ballmer_windows_me_msg, list("Yo man, what if, we like, uh, put drunkenness = max(drunkenness - (drunkenness * 0.04), 0) if(drunkenness >= 6) SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "drunk", /datum/mood_event/drunk) + if(prob(25)) + slurring += 2 jitteriness = max(jitteriness - 3, 0) + // throw_alert("drunk", /atom/movable/screen/alert/drunk) if(HAS_TRAIT(src, TRAIT_DRUNK_HEALING)) adjustBruteLoss(-0.12, FALSE) adjustFireLoss(-0.06, FALSE) + sound_environment_override = SOUND_ENVIRONMENT_PSYCHOTIC + else + SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "drunk") + clear_alert("drunk") + sound_environment_override = SOUND_ENVIRONMENT_NONE if(mind && (mind.assigned_role == "Scientist" || mind.assigned_role == "Research Director")) if(SSresearch.science_tech) diff --git a/code/modules/mob/living/carbon/monkey/monkey.dm b/code/modules/mob/living/carbon/monkey/monkey.dm index 1f3e0a1b81..d49059f839 100644 --- a/code/modules/mob/living/carbon/monkey/monkey.dm +++ b/code/modules/mob/living/carbon/monkey/monkey.dm @@ -6,7 +6,6 @@ icon_state = "" gender = NEUTER pass_flags = PASSTABLE - ventcrawler = VENTCRAWLER_NUDE mob_biotypes = MOB_ORGANIC|MOB_HUMANOID butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab/monkey = 5, /obj/item/stack/sheet/animalhide/monkey = 1) type_of_meat = /obj/item/reagent_containers/food/snacks/meat/slab/monkey @@ -29,6 +28,8 @@ create_bodyparts() create_internal_organs() + AddElement(/datum/element/ventcrawling, given_tier = VENTCRAWLER_NUDE) + . = ..() if (cubespawned) diff --git a/code/modules/mob/living/carbon/say.dm b/code/modules/mob/living/carbon/say.dm index 27d0e9cbaf..67c29b4211 100644 --- a/code/modules/mob/living/carbon/say.dm +++ b/code/modules/mob/living/carbon/say.dm @@ -10,6 +10,8 @@ /mob/living/carbon/can_speak_vocal(message) if(silent) return 0 + if(get_selected_language() == /datum/language/signlanguage && handcuffed) + return 0 return ..() /mob/living/carbon/could_speak_language(datum/language/language) diff --git a/code/modules/mob/living/carbon/status_procs.dm b/code/modules/mob/living/carbon/status_procs.dm index a47bb7fb4a..d602c25331 100644 --- a/code/modules/mob/living/carbon/status_procs.dm +++ b/code/modules/mob/living/carbon/status_procs.dm @@ -8,10 +8,12 @@ overlay_fullscreen("high", /obj/screen/fullscreen/high) throw_alert("high", /obj/screen/alert/high) SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "high", /datum/mood_event/high) + sound_environment_override = SOUND_ENVIRONMENT_DRUGGED else clear_fullscreen("high") clear_alert("high") SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "high") + sound_environment_override = SOUND_ENVIRONMENT_NONE /mob/living/carbon/set_drugginess(amount) druggy = max(amount, 0) diff --git a/code/modules/mob/living/death.dm b/code/modules/mob/living/death.dm index 688b0cf63e..be9cd0aabb 100644 --- a/code/modules/mob/living/death.dm +++ b/code/modules/mob/living/death.dm @@ -1,4 +1,4 @@ -/mob/living/gib(no_brain, no_organs, no_bodyparts) +/mob/living/gib(no_brain, no_organs, no_bodyparts, datum/explosion/was_explosion) var/prev_lying = lying if(stat != DEAD) death(1) @@ -6,22 +6,22 @@ if(!prev_lying) gib_animation() - spill_organs(no_brain, no_organs, no_bodyparts) + spill_organs(no_brain, no_organs, no_bodyparts, was_explosion) if(!no_bodyparts) - spread_bodyparts(no_brain, no_organs) + spread_bodyparts(no_brain, no_organs, was_explosion) for(var/X in implants) var/obj/item/implant/I = X qdel(I) - spawn_gibs(no_bodyparts) + spawn_gibs(no_bodyparts, null, was_explosion) qdel(src) /mob/living/proc/gib_animation() return -/mob/living/proc/spawn_gibs(with_bodyparts, atom/loc_override) +/mob/living/proc/spawn_gibs(with_bodyparts, atom/loc_override, datum/explosion/was_explosion) var/location = loc_override ? loc_override.drop_location() : drop_location() if(mob_biotypes & MOB_ROBOTIC) new /obj/effect/gibspawner/robot(location, src, get_static_viruses()) @@ -31,7 +31,7 @@ /mob/living/proc/spill_organs() return -/mob/living/proc/spread_bodyparts() +/mob/living/proc/spread_bodyparts(no_brain, no_organs, datum/explosion/was_explosion) return /mob/living/dust(just_ash, drop_items, force) diff --git a/code/modules/mob/living/life.dm b/code/modules/mob/living/life.dm index a6fdfdc793..a6453e58b7 100644 --- a/code/modules/mob/living/life.dm +++ b/code/modules/mob/living/life.dm @@ -24,7 +24,7 @@ break var/msg = "[key_name_admin(src)] [ADMIN_JMP(src)] was found to have no .loc with an attached client, if the cause is unknown it would be wise to ask how this was accomplished." message_admins(msg) - send2irc_adminless_only("Mob", msg, R_ADMIN) + send2tgs_adminless_only("Mob", msg, R_ADMIN) log_game("[key_name(src)] was found to have no .loc with an attached client.") // This is a temporary error tracker to make sure we've caught everything diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index fcc1d734a3..6aaa2cca87 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -866,7 +866,7 @@ if(istype(G)) strip_mod = G.strip_mod strip_silence = G.strip_silence - if (!strip_silence) + if(!strip_silence) who.visible_message("[src] tries to remove [who]'s [what.name].", \ "[src] tries to remove your [what.name].", target = src, target_message = "You try to remove [who]'s [what.name].") diff --git a/code/modules/mob/living/living_active_parry.dm b/code/modules/mob/living/living_active_parry.dm index 8d1ee38de6..10f8aaf2f4 100644 --- a/code/modules/mob/living/living_active_parry.dm +++ b/code/modules/mob/living/living_active_parry.dm @@ -2,7 +2,7 @@ /** * Determines if we can actively parry. */ -/obj/item/proc/can_active_parry() +/obj/item/proc/can_active_parry(mob/user) return block_parry_data && (item_flags & ITEM_CAN_PARRY) /** @@ -23,13 +23,16 @@ if(!(combat_flags & COMBAT_FLAG_PARRY_CAPABLE)) to_chat(src, "You are not something that can parry attacks.") return + if(!(mobility_flags & MOBILITY_STAND)) + to_chat(src, "You aren't able to parry without solid footing!") + return // Prioritize item, then martial art, then unarmed. // yanderedev else if time var/obj/item/using_item = get_active_held_item() var/datum/block_parry_data/data var/datum/tool var/method - if(using_item?.can_active_parry()) + if(using_item?.can_active_parry(src)) data = using_item.block_parry_data method = ITEM_PARRY tool = using_item @@ -50,9 +53,20 @@ using_item = backup method = ITEM_PARRY var/list/other_items = list() - if(SEND_SIGNAL(src, COMSIG_LIVING_ACTIVE_PARRY_START, method, tool, other_items) & COMPONENT_PREVENT_PARRY_START) + var/list/override = list() + if(SEND_SIGNAL(src, COMSIG_LIVING_ACTIVE_PARRY_START, method, tool, other_items, override) & COMPONENT_PREVENT_PARRY_START) to_chat(src, "Something is preventing you from parrying!") return + if(length(override)) + var/datum/thing = override[1] + var/_method = override[thing] + if(_method == ITEM_PARRY) + using_item = thing + method = ITEM_PARRY + data = using_item.block_parry_data + else if(_method == UNARMED_PARRY) + method = UNARMED_PARRY + data = thing if(!using_item && !method && length(other_items)) using_item = other_items[1] method = ITEM_PARRY @@ -94,7 +108,7 @@ */ /mob/living/proc/find_backup_parry_item() for(var/obj/item/I in held_items - get_active_held_item()) - if(I.can_active_parry()) + if(I.can_active_parry(src)) return I /** @@ -231,7 +245,7 @@ var/efficiency = data.get_parry_efficiency(attack_type, get_parry_time()) switch(parrying) if(ITEM_PARRY) - if(!active_parry_item.can_active_parry()) + if(!active_parry_item.can_active_parry(src)) return BLOCK_NONE . = active_parry_item.on_active_parry(src, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, return_list, efficiency, get_parry_time()) if(UNARMED_PARRY) @@ -243,6 +257,18 @@ if(efficiency <= 0) // Do not allow automatically handled/standardized parries that increase damage for now. return . |= BLOCK_SHOULD_PARTIAL_MITIGATE + if(efficiency >= data.parry_efficiency_perfect) + . |= data.perfect_parry_block_return_flags + if(data.perfect_parry_block_return_list) + return_list |= data.perfect_parry_block_return_list + else if(efficiency >= data.parry_efficiency_considered_successful) + . |= data.imperfect_parry_block_return_flags + if(data.imperfect_parry_block_return_list) + return_list |= data.imperfect_parry_block_return_list + else + . |= data.failed_parry_block_return_flags + if(data.failed_parry_block_return_list) + return_list |= data.failed_parry_block_return_list if(isnull(return_list[BLOCK_RETURN_MITIGATION_PERCENT])) // if one of the on_active_parry procs overrode. We don't have to worry about interference since parries are the first thing checked in the [do_run_block()] sequence. return_list[BLOCK_RETURN_MITIGATION_PERCENT] = clamp(efficiency, 0, 100) // do not allow > 100% or < 0% for now. if((return_list[BLOCK_RETURN_MITIGATION_PERCENT] >= 100) || (damage <= 0)) diff --git a/code/modules/mob/living/living_blocking_parrying.dm b/code/modules/mob/living/living_blocking_parrying.dm index e290956873..9e974177e5 100644 --- a/code/modules/mob/living/living_blocking_parrying.dm +++ b/code/modules/mob/living/living_blocking_parrying.dm @@ -157,6 +157,16 @@ GLOBAL_LIST_EMPTY(block_parry_data) /// Parry cooldown post-parry if failed. This is ADDED to parry_cooldown!!! var/parry_failed_cooldown_duration = 0 SECONDS + // Advanced + /// Flags added to return value + var/perfect_parry_block_return_flags = NONE + var/imperfect_parry_block_return_flags = NONE + var/failed_parry_block_return_flags = NONE + /// List appended to block return + var/perfect_parry_block_return_list + var/imperfect_parry_block_return_list + var/failed_parry_block_return_list + /** * Quirky proc to get average of flags in list that are in attack_type because why is attack_type a flag. */ diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm index 742d4d8624..386f1174e1 100644 --- a/code/modules/mob/living/living_defines.dm +++ b/code/modules/mob/living/living_defines.dm @@ -81,7 +81,6 @@ var/bloodcrawl = 0 //0 No blood crawling, BLOODCRAWL for bloodcrawling, BLOODCRAWL_EAT for crawling+mob devour var/holder = null //The holder for blood crawling - var/ventcrawler = 0 //0 No vent crawling, 1 vent crawling in the nude, 2 vent crawling always var/limb_destroyer = 0 //1 Sets AI behavior that allows mobs to target and dismember limbs with their basic attack. var/mob_size = MOB_SIZE_HUMAN diff --git a/code/modules/mob/living/living_movement.dm b/code/modules/mob/living/living_movement.dm index 9e093d391e..bafa38ec5e 100644 --- a/code/modules/mob/living/living_movement.dm +++ b/code/modules/mob/living/living_movement.dm @@ -101,6 +101,7 @@ if(lying && !buckled && prob(getBruteLoss()*200/maxHealth)) makeTrail(newloc, T, old_direction) + if(causes_dirt_buildup_on_floor && (movement_type & GROUND)) dirt_buildup() diff --git a/code/modules/mob/living/living_sprint.dm b/code/modules/mob/living/living_sprint.dm index 3ef67c9edd..728645c3eb 100644 --- a/code/modules/mob/living/living_sprint.dm +++ b/code/modules/mob/living/living_sprint.dm @@ -26,6 +26,8 @@ update_sprint_icon() /mob/living/proc/enable_sprint_mode(update_icon = TRUE) + if(!CONFIG_GET(flag/sprint_enabled)) + return if(combat_flags & COMBAT_FLAG_SPRINT_ACTIVE) return ENABLE_BITFIELD(combat_flags, COMBAT_FLAG_SPRINT_ACTIVE) @@ -61,6 +63,8 @@ update_sprint_icon() /mob/living/proc/user_toggle_intentional_sprint_mode() + if(!CONFIG_GET(flag/sprint_enabled)) + return var/old = (combat_flags & COMBAT_FLAG_SPRINT_TOGGLED) if(old) if(combat_flags & COMBAT_FLAG_SPRINT_FORCED) diff --git a/code/modules/mob/living/login.dm b/code/modules/mob/living/login.dm index 931f87eb7c..2b1c2de17a 100644 --- a/code/modules/mob/living/login.dm +++ b/code/modules/mob/living/login.dm @@ -17,10 +17,6 @@ if (isturf(T)) update_z(T.z) - //Vents - if(ventcrawler) - to_chat(src, "You can ventcrawl! Use alt+click on vents to quickly travel about the station.") - if(ranged_ability) ranged_ability.add_ranged_ability(src, "You currently have [ranged_ability] active!") if((vore_flags & VORE_INIT) && !(vore_flags & VOREPREF_INIT)) //Vore's been initialized, voreprefs haven't. If this triggers then that means that voreprefs failed to load due to the client being missing. diff --git a/code/modules/mob/living/say.dm b/code/modules/mob/living/say.dm index 4c03e74d70..9646796802 100644 --- a/code/modules/mob/living/say.dm +++ b/code/modules/mob/living/say.dm @@ -329,8 +329,26 @@ GLOBAL_LIST_INIT(department_radio_keys, list( return 1 /mob/living/proc/can_speak_vocal(message) //Check AFTER handling of xeno and ling channels - if(HAS_TRAIT(src, TRAIT_MUTE)) + var/obj/item/bodypart/leftarm = get_bodypart(BODY_ZONE_L_ARM) + var/obj/item/bodypart/rightarm = get_bodypart(BODY_ZONE_R_ARM) + if(HAS_TRAIT(src, TRAIT_MUTE) && get_selected_language() != /datum/language/signlanguage) return 0 + + if (get_selected_language() == /datum/language/signlanguage) + var/left_disabled = FALSE + var/right_disabled = FALSE + if (istype(leftarm)) // Need to check if the arms exist first before checking if they are disabled or else it will runtime + if (leftarm.is_disabled()) + left_disabled = TRUE + else + left_disabled = TRUE + if (istype(rightarm)) + if (rightarm.is_disabled()) + right_disabled = TRUE + else + right_disabled = TRUE + if (left_disabled && right_disabled) // We want this to only return false if both arms are either missing or disabled since you could technically sign one-handed. + return 0 if(is_muzzled()) return 0 diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm index fbf2d27c31..e0e1d91ba9 100644 --- a/code/modules/mob/living/silicon/ai/ai.dm +++ b/code/modules/mob/living/silicon/ai/ai.dm @@ -911,10 +911,10 @@ if(!istype(apc) || QDELETED(apc) || apc.stat & BROKEN) to_chat(src, "Hack aborted. The designated APC no longer exists on the power network.") - playsound(get_turf(src), 'sound/machines/buzz-two.ogg', 50, 1) + playsound(get_turf(src), 'sound/machines/buzz-two.ogg', 50, TRUE, ignore_walls = FALSE) else if(apc.aidisabled) to_chat(src, "Hack aborted. \The [apc] is no longer responding to our systems.") - playsound(get_turf(src), 'sound/machines/buzz-sigh.ogg', 50, 1) + playsound(get_turf(src), 'sound/machines/buzz-sigh.ogg', 50, TRUE, ignore_walls = FALSE) else malf_picker.processing_time += 10 @@ -923,7 +923,7 @@ apc.locked = TRUE apc.coverlocked = TRUE - playsound(get_turf(src), 'sound/machines/ding.ogg', 50, 1) + playsound(get_turf(src), 'sound/machines/ding.ogg', 50, TRUE, ignore_walls = FALSE) to_chat(src, "Hack complete. \The [apc] is now under your exclusive control.") apc.update_icon() @@ -1039,3 +1039,6 @@ if(current && eyeobj) return eyeobj.emote(act, m_type, message, intentional, forced = TRUE) return ..() + +/mob/living/silicon/ai/zMove(dir, feedback = FALSE) + . = eyeobj.zMove(dir, feedback) diff --git a/code/modules/mob/living/silicon/ai/ai_portrait_picker.dm b/code/modules/mob/living/silicon/ai/ai_portrait_picker.dm new file mode 100644 index 0000000000..ba785251b7 --- /dev/null +++ b/code/modules/mob/living/silicon/ai/ai_portrait_picker.dm @@ -0,0 +1,78 @@ + +//Portrait picker! It's a tgui window that lets you look through all the portraits, and choose one as your AI. + +//very similar to centcom_podlauncher in terms of how this is coded, so i kept a lot of comments from it +//^ wow! it's the second time i've said this! i'm a real coder now, copying my statement of copying other people's stuff. + + +#define TAB_LIBRARY 1 +#define TAB_SECURE 2 +#define TAB_PRIVATE 3 + +/datum/portrait_picker + var/client/holder //client of whoever is using this datum + +/datum/portrait_picker/New(user)//user can either be a client or a mob due to byondcode(tm) + if (istype(user, /client)) + var/client/user_client = user + holder = user_client //if its a client, assign it to holder + else + var/mob/user_mob = user + holder = user_mob.client //if its a mob, assign the mob's client to holder + +/datum/portrait_picker/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "PortraitPicker") + ui.open() + +/datum/portrait_picker/ui_close() + qdel(src) + +/datum/portrait_picker/ui_state(mob/user) + return GLOB.conscious_state + +/datum/portrait_picker/ui_assets(mob/user) + return list( + get_asset_datum(/datum/asset/simple/portraits/library), + get_asset_datum(/datum/asset/simple/portraits/library_secure), + get_asset_datum(/datum/asset/simple/portraits/library_private) + ) + +/datum/portrait_picker/ui_data(mob/user) + var/list/data = list() + data["library"] = SSpersistence.paintings["library"] ? SSpersistence.paintings["library"] : 0 + data["library_secure"] = SSpersistence.paintings["library_secure"] ? SSpersistence.paintings["library_secure"] : 0 + data["library_private"] = SSpersistence.paintings["library_private"] ? SSpersistence.paintings["library_private"] : 0 //i'm gonna regret this, won't i? + return data + +/datum/portrait_picker/ui_act(action, params) + . = ..() + if(.) + return + switch(action) + if("select") + var/list/tab2key = list(TAB_LIBRARY = "library", TAB_SECURE = "library_secure", TAB_PRIVATE = "library_private") + var/folder = tab2key[params["tab"]] + var/list/current_list = SSpersistence.paintings[folder] + var/list/chosen_portrait = current_list[params["selected"]] + var/png = "data/paintings/[folder]/[chosen_portrait["md5"]].png" + var/icon/portrait_icon = new(png) + var/mob/living/ai = holder.mob + var/w = portrait_icon.Width() + var/h = portrait_icon.Height() + var/mutable_appearance/MA = mutable_appearance(portrait_icon) + if(w == 23 || h == 23) + to_chat(ai, "Small note: 23x23 Portraits are accepted, but they do not fit perfectly inside the display frame.") + MA.pixel_x = 5 + MA.pixel_y = 5 + else if(w == 24 || h == 24) + to_chat(ai, "Portrait Accepted. Enjoy!") + MA.pixel_x = 4 + MA.pixel_y = 4 + else + to_chat(ai, "Sorry, only 23x23 and 24x24 Portraits are accepted.") + return + ai.cut_overlays() //so people can't keep repeatedly select portraits to add stacking overlays + ai.icon_state = "ai-portrait-active"//background + ai.add_overlay(MA) diff --git a/code/modules/mob/living/silicon/ai/freelook/eye.dm b/code/modules/mob/living/silicon/ai/freelook/eye.dm index 17de811cdd..dcaa28b39c 100644 --- a/code/modules/mob/living/silicon/ai/freelook/eye.dm +++ b/code/modules/mob/living/silicon/ai/freelook/eye.dm @@ -94,6 +94,25 @@ if(ai.master_multicam) ai.master_multicam.refresh_view() +//it uses setLoc not forceMove, talks to the sillycone and not the camera mob +/mob/camera/aiEye/zMove(dir, feedback = FALSE) + if(dir != UP && dir != DOWN) + return FALSE + var/turf/target = get_step_multiz(src, dir) + if(!target) + if(feedback) + to_chat(ai, "There's nowhere to go in that direction!") + return FALSE + if(!canZMove(dir, target)) + if(feedback) + to_chat(ai, "You couldn't move there!") + return FALSE + setLoc(target, TRUE) + return TRUE + +/mob/camera/aiEye/canZMove(direction, turf/target) //cameras do not respect these FLOORS you speak so much of + return TRUE + /mob/camera/aiEye/Move() return 0 diff --git a/code/modules/mob/living/silicon/pai/pai.dm b/code/modules/mob/living/silicon/pai/pai.dm index 786c121ad6..4e33fba208 100644 --- a/code/modules/mob/living/silicon/pai/pai.dm +++ b/code/modules/mob/living/silicon/pai/pai.dm @@ -11,7 +11,7 @@ maxHealth = 500 layer = BELOW_MOB_LAYER var/obj/item/instrument/piano_synth/internal_instrument - silicon_privileges = PRIVILEDGES_PAI + silicon_privileges = PRIVILEGES_PAI var/network = "ss13" var/obj/machinery/camera/current = null @@ -143,6 +143,10 @@ custom_holoform.Grant(src) emitter_next_use = world.time + 10 SECONDS +/mob/living/silicon/pai/deployed/Initialize() + . = ..() + fold_out(TRUE) + /mob/living/silicon/pai/ComponentInitialize() . = ..() if(possible_chassis[chassis]) @@ -426,6 +430,10 @@ //Misc .["Cyborg - Misc (dog - blade)"] = process_holoform_icon_filter(icon('modular_citadel/icons/mob/widerobot.dmi', "blade"), HOLOFORM_FILTER_PAI, FALSE) + // Gorillas + .["Gorilla (standing)"] = process_holoform_icon_filter(icon('icons/mob/gorilla.dmi', "standing"), HOLOFORM_FILTER_PAI, FALSE) + .["Gorilla (crawling)"] = process_holoform_icon_filter(icon('icons/mob/gorilla.dmi', "crawling"), HOLOFORM_FILTER_PAI, FALSE) + /mob/living/silicon/pai/proc/default_chassis_pixel_offsets_x() . = list() //Engi diff --git a/code/modules/mob/living/silicon/robot/death.dm b/code/modules/mob/living/silicon/robot/death.dm index 6a5338c1f5..4fd2888e00 100644 --- a/code/modules/mob/living/silicon/robot/death.dm +++ b/code/modules/mob/living/silicon/robot/death.dm @@ -16,7 +16,9 @@ /mob/living/silicon/robot/death(gibbed) if(stat == DEAD) return - + if(!gibbed) + logevent("FATAL -- SYSTEM HALT") + modularInterface.shutdown_computer() . = ..() locked = FALSE //unlock cover @@ -24,7 +26,7 @@ update_mobility() if(!QDELETED(builtInCamera) && builtInCamera.status) builtInCamera.toggle_cam(src,0) - update_headlamp(1) //So borg lights are disabled when killed. + toggle_headlamp(TRUE) //So borg lights are disabled when killed. uneq_all() // particularly to ensure sight modes are cleared diff --git a/code/modules/mob/living/silicon/robot/inventory.dm b/code/modules/mob/living/silicon/robot/inventory.dm index 9b964ef188..b11737659c 100644 --- a/code/modules/mob/living/silicon/robot/inventory.dm +++ b/code/modules/mob/living/silicon/robot/inventory.dm @@ -1,11 +1,12 @@ //These procs handle putting stuff in your hand. It's probably best to use these rather than setting stuff manually //as they handle all relevant stuff like adding it to the player's screen and such -//Returns the thing in our active hand (whatever is in our active module-slot, in this case) -//This proc has been butchered into a proc that overrides borg item holding for the sake of making grippers work. -//I'd be immensely thankful if anyone can figure out a less obtuse way of making grippers work without breaking functionality. +/** + * Returns the thing in our active hand (whatever is in our active module-slot, in this case) + */ /mob/living/silicon/robot/get_active_held_item() var/item = module_active + // snowflake handler for the gripper if(istype(item, /obj/item/weapon/gripper)) var/obj/item/weapon/gripper/G = item if(G.wrapped) @@ -15,230 +16,416 @@ item = G.wrapped return item return module_active +/** + * Parent proc - triggers when an item/module is unequipped from a cyborg. + */ +/obj/item/proc/cyborg_unequip(mob/user) + return -/mob/living/silicon/robot/proc/uneq_module(obj/item/O) - if(!O) - return 0 - O.mouse_opacity = MOUSE_OPACITY_OPAQUE - if(istype(O, /obj/item/borg/sight)) - var/obj/item/borg/sight/S = O - sight_mode &= ~S.sight_mode +/** + * Finds the first available slot and attemps to put item item_module in it. + * + * Arguments + * * item_module - the item being equipped to a slot. + */ +/mob/living/silicon/robot/proc/activate_module(obj/item/item_module) + if(QDELETED(item_module)) + CRASH("activate_module called with improper item_module") + + if(!(item_module in module.modules)) + CRASH("activate_module called with item_module not in module.modules") + + if(activated(item_module)) + to_chat(src, "That module is already activated.") + return FALSE + + if(disabled_modules & BORG_MODULE_ALL_DISABLED) + to_chat(src, "All modules are disabled!") + return FALSE + + /// What's the first free slot for the borg? + var/first_free_slot = !held_items[1] ? 1 : (!held_items[2] ? 2 : (!held_items[3] ? 3 : null)) + + if(!first_free_slot || is_invalid_module_number(first_free_slot)) + to_chat(src, "Deactivate a module first!") + return FALSE + + return equip_module_to_slot(item_module, first_free_slot) + +/** + * Is passed an item and a module slot. Equips the item to that borg slot. + * + * Arguments + * * item_module - the item being equipped to a slot + * * module_num - the slot number being equipped to. + */ +/mob/living/silicon/robot/proc/equip_module_to_slot(obj/item/item_module, module_num) + var/storage_was_closed = FALSE //Just to be consistant and all + if(!shown_robot_modules) //Tools may be invisible if the collection is hidden + hud_used.toggle_show_robot_modules() + storage_was_closed = TRUE + switch(module_num) + if(1) + item_module.screen_loc = inv1.screen_loc + if(2) + item_module.screen_loc = inv2.screen_loc + if(3) + item_module.screen_loc = inv3.screen_loc + + held_items[module_num] = item_module + item_module.equipped(src, ITEM_SLOT_HANDS) + item_module.mouse_opacity = initial(item_module.mouse_opacity) + item_module.layer = ABOVE_HUD_LAYER + item_module.plane = ABOVE_HUD_PLANE + item_module.forceMove(src) + + if(istype(item_module, /obj/item/borg/sight)) + var/obj/item/borg/sight/borg_sight = item_module + sight_mode |= borg_sight.sight_mode + update_sight() + + observer_screen_update(item_module, TRUE) + + if(storage_was_closed) + hud_used.toggle_show_robot_modules() + return TRUE + +/** + * Unequips item item_module from slot module_num. Deletes it if delete_after = TRUE. + * + * Arguments + * * item_module - the item being unequipped + * * module_num - the slot number being unequipped. + */ +/mob/living/silicon/robot/proc/unequip_module_from_slot(obj/item/item_module, module_num) + if(QDELETED(item_module)) + CRASH("unequip_module_from_slot called with improper item_module") + + if(!(item_module in module.modules)) + CRASH("unequip_module_from_slot called with item_module not in module.modules") + + item_module.mouse_opacity = MOUSE_OPACITY_OPAQUE + + if(istype(item_module, /obj/item/storage/bag/tray/)) + SEND_SIGNAL(item_module, COMSIG_TRY_STORAGE_QUICK_EMPTY) + if(istype(item_module, /obj/item/borg/sight)) + var/obj/item/borg/sight/borg_sight = item_module + sight_mode &= ~borg_sight.sight_mode update_sight() - else if(istype(O, /obj/item/storage/bag/tray/)) - SEND_SIGNAL(O, COMSIG_TRY_STORAGE_QUICK_EMPTY) //CITADEL EDIT reee proc, Dogborg modules - if(istype(O,/obj/item/gun/energy/laser/cyborg)) + if(istype(item_module, /obj/item/gun/energy/laser/cyborg)) laser = FALSE update_icons() - else if(istype(O,/obj/item/gun/energy/disabler/cyborg) || istype(O,/obj/item/gun/energy/e_gun/advtaser/cyborg)) + if(istype(item_module, /obj/item/gun/energy/disabler/cyborg) || istype(item_module, /obj/item/gun/energy/e_gun/advtaser/cyborg)) disabler = FALSE update_icons() //PUT THE GUN AWAY - else if(istype(O,/obj/item/dogborg/sleeper)) + if(istype(item_module, /obj/item/dogborg/sleeper)) sleeper_g = FALSE sleeper_r = FALSE update_icons() - var/obj/item/dogborg/sleeper/S = O + var/obj/item/dogborg/sleeper/S = item_module S.go_out() //this should stop edgecase deletions //END CITADEL EDIT + if(client) - client.screen -= O - observer_screen_update(O,FALSE) + client.screen -= item_module - if(module_active == O) + if(module_active == item_module) module_active = null - if(held_items[1] == O) - inv1.icon_state = "inv1" - held_items[1] = null - else if(held_items[2] == O) - inv2.icon_state = "inv2" - held_items[2] = null - else if(held_items[3] == O) - inv3.icon_state = "inv3" - held_items[3] = null - if(O.item_flags & DROPDEL) - O.item_flags &= ~DROPDEL //we shouldn't HAVE things with DROPDEL_1 in our modules, but better safe than runtiming horribly + switch(module_num) + if(1) + if(!(disabled_modules & BORG_MODULE_ALL_DISABLED)) + inv1.icon_state = initial(inv1.icon_state) + if(2) + if(!(disabled_modules & BORG_MODULE_TWO_DISABLED)) + inv2.icon_state = initial(inv2.icon_state) + if(3) + if(!(disabled_modules & BORG_MODULE_THREE_DISABLED)) + inv3.icon_state = initial(inv3.icon_state) - O.forceMove(module) //Return item to module so it appears in its contents, so it can be taken out again. + if(item_module.item_flags & DROPDEL) + item_module.item_flags &= ~DROPDEL //we shouldn't HAVE things with DROPDEL_1 in our modules, but better safe than runtiming horribly + held_items[module_num] = null + item_module.cyborg_unequip(src) + item_module.forceMove(module) //Return item to module so it appears in its contents, so it can be taken out again. + + observer_screen_update(item_module, FALSE) hud_used.update_robot_modules_display() - return 1 + return TRUE -/mob/living/silicon/robot/proc/activate_module(obj/item/O) - . = FALSE - if(!(O in module.modules)) - return - //CITADEL EDIT Dogborg lasers - if(istype(O,/obj/item/gun/energy/laser/cyborg)) - laser = TRUE - update_icons() //REEEEEEACH FOR THE SKY - if(istype(O,/obj/item/gun/energy/disabler/cyborg) || istype(O,/obj/item/gun/energy/e_gun/advtaser/cyborg)) - disabler = TRUE - update_icons() - //END CITADEL EDIT - if(activated(O)) - to_chat(src, "That module is already activated.") - return - if(!held_items[1] && health >= -maxHealth*0.5) - held_items[1] = O - O.screen_loc = inv1.screen_loc - . = TRUE - else if(!held_items[2] && health >= 0) - held_items[2] = O - O.screen_loc = inv2.screen_loc - . = TRUE - else if(!held_items[3] && health >= maxHealth*0.5) - held_items[3] = O - O.screen_loc = inv3.screen_loc - . = TRUE - else - to_chat(src, "You need to disable a module first!") - if(.) - O.equipped(src, SLOT_HANDS) - O.mouse_opacity = initial(O.mouse_opacity) - O.layer = ABOVE_HUD_LAYER - O.plane = ABOVE_HUD_PLANE - observer_screen_update(O,TRUE) - O.forceMove(src) - if(istype(O, /obj/item/borg/sight)) - var/obj/item/borg/sight/S = O - sight_mode |= S.sight_mode - update_sight() +/** + * Breaks the slot number, changing the icon. + * + * Arguments + * * module_num - the slot number being repaired. + */ +/mob/living/silicon/robot/proc/break_cyborg_slot(module_num) + if(is_invalid_module_number(module_num, TRUE)) + return FALSE + if(held_items[module_num]) //If there's a held item, unequip it first. + if(!unequip_module_from_slot(held_items[module_num], module_num)) //If we fail to unequip it, then don't continue + return FALSE -/mob/living/silicon/robot/proc/observer_screen_update(obj/item/I,add = TRUE) - if(observers && observers.len) + switch(module_num) + if(1) + if(disabled_modules & BORG_MODULE_ALL_DISABLED) + return FALSE + + inv1.icon_state = "[initial(inv1.icon_state)] +b" + disabled_modules |= BORG_MODULE_ALL_DISABLED + + playsound(src, 'sound/machines/warning-buzzer.ogg', 75, TRUE, TRUE) + audible_message("[src] sounds an alarm! \"CRITICAL ERROR: ALL modules OFFLINE.\"") + + if(builtInCamera) + builtInCamera.status = FALSE + to_chat(src, "CRITICAL ERROR: Built in security camera OFFLINE.") + + to_chat(src, "CRITICAL ERROR: ALL modules OFFLINE.") + + if(2) + if(disabled_modules & BORG_MODULE_TWO_DISABLED) + return FALSE + + inv2.icon_state = "[initial(inv2.icon_state)] +b" + disabled_modules |= BORG_MODULE_TWO_DISABLED + + playsound(src, 'sound/machines/warning-buzzer.ogg', 60, TRUE, TRUE) + audible_message("[src] sounds an alarm! \"SYSTEM ERROR: Module [module_num] OFFLINE.\"") + to_chat(src, "SYSTEM ERROR: Module [module_num] OFFLINE.") + + if(3) + if(disabled_modules & BORG_MODULE_THREE_DISABLED) + return FALSE + + inv3.icon_state = "[initial(inv3.icon_state)] +b" + disabled_modules |= BORG_MODULE_THREE_DISABLED + + playsound(src, 'sound/machines/warning-buzzer.ogg', 50, TRUE, TRUE) + audible_message("[src] sounds an alarm! \"SYSTEM ERROR: Module [module_num] OFFLINE.\"") + to_chat(src, "SYSTEM ERROR: Module [module_num] OFFLINE.") + + return TRUE + +/** + * Breaks all of a cyborg's slots. + */ +/mob/living/silicon/robot/proc/break_all_cyborg_slots() + for(var/cyborg_slot in 1 to 3) + break_cyborg_slot(cyborg_slot) + +/** + * Repairs the slot number, updating the icon. + * + * Arguments + * * module_num - the module number being repaired. + */ +/mob/living/silicon/robot/proc/repair_cyborg_slot(module_num) + if(is_invalid_module_number(module_num, TRUE)) + return FALSE + + switch(module_num) + if(1) + if(!(disabled_modules & BORG_MODULE_ALL_DISABLED)) + return FALSE + + inv1.icon_state = initial(inv1.icon_state) + disabled_modules &= ~BORG_MODULE_ALL_DISABLED + if(builtInCamera) + builtInCamera.status = TRUE + to_chat(src, "You hear your built in security camera focus adjust as it comes back online!") + if(2) + if(!(disabled_modules & BORG_MODULE_TWO_DISABLED)) + return FALSE + + inv2.icon_state = initial(inv2.icon_state) + disabled_modules &= ~BORG_MODULE_TWO_DISABLED + if(3) + if(!(disabled_modules & BORG_MODULE_THREE_DISABLED)) + return FALSE + + inv3.icon_state = initial(inv3.icon_state) + disabled_modules &= ~BORG_MODULE_THREE_DISABLED + + to_chat(src, "ERROR CLEARED: Module [module_num] back online.") + + return TRUE + +/** + * Repairs all slots. Unbroken slots are unaffected. + */ +/mob/living/silicon/robot/proc/repair_all_cyborg_slots() + for(var/cyborg_slot in 1 to 3) + repair_cyborg_slot(cyborg_slot) + +/** + * Updates the observers's screens with cyborg itemss. + * Arguments + * * item_module - the item being added or removed from the screen + * * add - whether or not the item is being added, or removed. + */ +/mob/living/silicon/robot/proc/observer_screen_update(obj/item/item_module, add = TRUE) + if(observers?.len) for(var/M in observers) var/mob/dead/observe = M if(observe.client && observe.client.eye == src) if(add) - observe.client.screen += I + observe.client.screen += item_module else - observe.client.screen -= I + observe.client.screen -= item_module else observers -= observe if(!observers.len) observers = null break +/** + * Unequips the active held item, if there is one. + */ /mob/living/silicon/robot/proc/uneq_active() - uneq_module(module_active) + if(module_active) + unequip_module_from_slot(module_active, get_selected_module()) +/** + * Unequips all held items. + */ /mob/living/silicon/robot/proc/uneq_all() - for(var/obj/item/I in held_items) - uneq_module(I) + for(var/cyborg_slot in 1 to 3) + if(!held_items[cyborg_slot]) + continue + unequip_module_from_slot(held_items[cyborg_slot], cyborg_slot) -/mob/living/silicon/robot/proc/activated(obj/item/O) - if(O in held_items) +/** + * Checks if the item is currently in a slot. + * + * If the item is found in a slot, this returns TRUE. Otherwise, it returns FALSE + * Arguments + * * item_module - the item being checked + */ +/mob/living/silicon/robot/proc/activated(obj/item/item_module) + if(item_module in held_items) return TRUE return FALSE -//Helper procs for cyborg modules on the UI. -//These are hackish but they help clean up code elsewhere. +/** + * Checks if the provided module number is a valid number. + * + * If the number is between 1 and 3 (if check_all_slots is true) or between 1 and the number of disabled + * modules (if check_all_slots is false), then it returns FALSE. Otherwise, it returns TRUE. + * Arguments + * * module_num - the passed module num that is checked for validity. + * * check_all_slots - TRUE = the proc checks all slots | FALSE = the proc only checks un-disabled slots + */ +/mob/living/silicon/robot/proc/is_invalid_module_number(module_num, check_all_slots = FALSE) + if(!module_num) + return TRUE -//module_selected(module) - Checks whether the module slot specified by "module" is currently selected. -/mob/living/silicon/robot/proc/module_selected(module) //Module is 1-3 - return module == get_selected_module() + /// The number of module slots we're checking + var/max_number = 3 + if(!check_all_slots) + if(disabled_modules & BORG_MODULE_ALL_DISABLED) + max_number = 0 + else if(disabled_modules & BORG_MODULE_TWO_DISABLED) + max_number = 1 + else if(disabled_modules & BORG_MODULE_THREE_DISABLED) + max_number = 2 -//module_active(module) - Checks whether there is a module active in the slot specified by "module". -/mob/living/silicon/robot/proc/module_active(module) //Module is 1-3 - if(module < 1 || module > 3) - return FALSE + return module_num < 1 || module_num > max_number - if(LAZYLEN(held_items) >= module) - if(held_items[module]) - return TRUE - return FALSE - -//get_selected_module() - Returns the slot number of the currently selected module. Returns 0 if no modules are selected. +/** + * Returns the slot number of the selected module, or zero if no modules are selected. + */ /mob/living/silicon/robot/proc/get_selected_module() if(module_active) return held_items.Find(module_active) return 0 -//select_module(module) - Selects the module slot specified by "module" -/mob/living/silicon/robot/proc/select_module(module) //Module is 1-3 - if(module < 1 || module > 3) - return +/** + * Selects the module in the slot module_num. + * Arguments + * * module_num - the slot number being selected + */ +/mob/living/silicon/robot/proc/select_module(module_num) + if(is_invalid_module_number(module_num) || !held_items[module_num]) //If the slot number is invalid, or there's nothing there, we have nothing to equip + return FALSE - if(!module_active(module)) - return - - switch(module) + switch(module_num) if(1) - if(module_active != held_items[module]) - inv1.icon_state = "inv1 +a" - inv2.icon_state = "inv2" - inv3.icon_state = "inv3" + if(module_active != held_items[module_num]) + inv1.icon_state = "[initial(inv1.icon_state)] +a" if(2) - if(module_active != held_items[module]) - inv1.icon_state = "inv1" - inv2.icon_state = "inv2 +a" - inv3.icon_state = "inv3" + if(module_active != held_items[module_num]) + inv2.icon_state = "[initial(inv2.icon_state)] +a" if(3) - if(module_active != held_items[module]) - inv1.icon_state = "inv1" - inv2.icon_state = "inv2" - inv3.icon_state = "inv3 +a" - module_active = held_items[module] + if(module_active != held_items[module_num]) + inv3.icon_state = "[initial(inv3.icon_state)] +a" + module_active = held_items[module_num] + return TRUE -//deselect_module(module) - Deselects the module slot specified by "module" -/mob/living/silicon/robot/proc/deselect_module(module) //Module is 1-3 - if(module < 1 || module > 3) - return - - if(!module_active(module)) - return - - switch(module) +/** + * Deselects the module in the slot module_num. + * Arguments + * * module_num - the slot number being de-selected + */ +/mob/living/silicon/robot/proc/deselect_module(module_num) + switch(module_num) if(1) - if(module_active == held_items[module]) - inv1.icon_state = "inv1" + if(module_active == held_items[module_num]) + inv1.icon_state = initial(inv1.icon_state) if(2) - if(module_active == held_items[module]) - inv2.icon_state = "inv2" + if(module_active == held_items[module_num]) + inv2.icon_state = initial(inv2.icon_state) if(3) - if(module_active == held_items[module]) - inv3.icon_state = "inv3" + if(module_active == held_items[module_num]) + inv3.icon_state = initial(inv3.icon_state) module_active = null + return TRUE -//toggle_module(module) - Toggles the selection of the module slot specified by "module". -/mob/living/silicon/robot/proc/toggle_module(module) //Module is 1-3 - if(module < 1 || module > 3) - return +/** + * Toggles selection of the module in the slot module_num. + * Arguments + * * module_num - the slot number being toggled + */ +/mob/living/silicon/robot/proc/toggle_module(module_num) + if(is_invalid_module_number(module_num)) + return FALSE - if(module_selected(module)) - deselect_module(module) - else - if(module_active(module)) - select_module(module) - else - deselect_module(get_selected_module()) //If we can't do select anything, at least deselect the current module. - return + if(module_num == get_selected_module()) + deselect_module(module_num) + return TRUE -//cycle_modules() - Cycles through the list of selected modules. + if(module_active != held_items[module_num]) + deselect_module(get_selected_module()) + + return select_module(module_num) + +/** + * Cycles through the list of enabled modules, deselecting the current one and selecting the next one. + */ /mob/living/silicon/robot/proc/cycle_modules() var/slot_start = get_selected_module() + var/slot_num if(slot_start) deselect_module(slot_start) //Only deselect if we have a selected slot. - - var/slot_num - if(slot_start == 0) + slot_num = slot_start + 1 + else slot_num = 1 slot_start = 4 - else - slot_num = slot_start + 1 while(slot_num != slot_start) //If we wrap around without finding any free slots, just give up. - if(module_active(slot_num)) - select_module(slot_num) + if(select_module(slot_num)) return slot_num++ if(slot_num > 4) // not >3 otherwise cycling with just one item on module 3 wouldn't work slot_num = 1 //Wrap around. - - /mob/living/silicon/robot/swap_hand() cycle_modules() + +/mob/living/silicon/robot/can_hold_items(obj/item/I) + return (I && (I in module.modules)) //Only if it's part of our module. + diff --git a/code/modules/mob/living/silicon/robot/life.dm b/code/modules/mob/living/silicon/robot/life.dm index 0feb8b98b7..9075af2dbd 100644 --- a/code/modules/mob/living/silicon/robot/life.dm +++ b/code/modules/mob/living/silicon/robot/life.dm @@ -8,22 +8,21 @@ /mob/living/silicon/robot/proc/handle_robot_cell() if(stat != DEAD) if(low_power_mode) - if(cell && cell.charge) - low_power_mode = 0 - update_headlamp() + if(cell?.charge) + low_power_mode = FALSE else if(stat == CONSCIOUS) use_power() /mob/living/silicon/robot/proc/use_power() - if(cell && cell.charge) + if(cell?.charge) if(cell.charge <= 100) uneq_all() - var/amt = clamp((lamp_intensity - 2) * 2,1,cell.charge) //Always try to use at least one charge per tick, but allow it to completely drain the cell. + var/amt = clamp((lamp_enabled * lamp_intensity),1,cell.charge) //Lamp will use a max of 5 charge, depending on brightness of lamp. If lamp is off, borg systems consume 1 point of charge, or the rest of the cell if it's lower than that. cell.use(amt) //Usage table: 1/tick if off/lowest setting, 4 = 4/tick, 6 = 8/tick, 8 = 12/tick, 10 = 16/tick else uneq_all() - low_power_mode = 1 - update_headlamp() + low_power_mode = TRUE + toggle_headlamp(TRUE) diag_hud_set_borgcell() /mob/living/silicon/robot/proc/handle_robot_hud_updates() diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm index 7db417b8fa..38f93f9ffc 100644 --- a/code/modules/mob/living/silicon/robot/robot.dm +++ b/code/modules/mob/living/silicon/robot/robot.dm @@ -15,7 +15,7 @@ wires = new /datum/wires/robot(src) AddElement(/datum/element/empprotection, EMP_PROTECT_WIRES) - + // AddElement(/datum/element/ridable, /datum/component/riding/creature/cyborg) RegisterSignal(src, COMSIG_PROCESS_BORGCHARGER_OCCUPANT, .proc/charge) robot_modules_background = new() @@ -23,10 +23,16 @@ robot_modules_background.layer = HUD_LAYER //Objects that appear on screen are on layer ABOVE_HUD_LAYER, UI should be just below it. robot_modules_background.plane = HUD_PLANE - ident = rand(1, 999) + inv1 = new /obj/screen/robot/module1() + inv2 = new /obj/screen/robot/module2() + inv3 = new /obj/screen/robot/module3() - if(!cell) - cell = new /obj/item/stock_parts/cell/high(src) + previous_health = health + + if(ispath(cell)) + cell = new cell(src) + + create_modularInterface() if(lawupdate) make_laws() @@ -63,18 +69,23 @@ mmi.brainmob.real_name = src.real_name mmi.brainmob.container = mmi - updatename() + INVOKE_ASYNC(src, .proc/updatename) - equippable_hats = typecacheof(equippable_hats) - - playsound(loc, 'sound/voice/liveagain.ogg', 75, 1) + playsound(loc, 'sound/voice/liveagain.ogg', 75, TRUE) aicamera = new/obj/item/camera/siliconcam/robot_camera(src) toner = tonermax diag_hud_set_borgcell() + logevent("System brought online.") add_verb(src, /mob/living/proc/lay_down) //CITADEL EDIT gimmie rest verb kthx add_verb(src, /mob/living/silicon/robot/proc/rest_style) +/mob/living/silicon/robot/proc/create_modularInterface() + if(!modularInterface) + modularInterface = new /obj/item/modular_computer/tablet/integrated(src) + modularInterface.layer = ABOVE_HUD_PLANE + modularInterface.plane = ABOVE_HUD_PLANE + //If there's an MMI in the robot, have it ejected when the mob goes away. --NEO /mob/living/silicon/robot/Destroy() var/atom/T = drop_location()//To hopefully prevent run time errors. @@ -93,28 +104,31 @@ ghostize() stack_trace("Borg MMI lacked a brainmob") mmi = null - //CITADEL EDIT: Cyborgs drop encryption keys on destroy - if(istype(radio) && istype(radio.keyslot)) - radio.keyslot.forceMove(T) - radio.keyslot = null - //END CITADEL EDIT + if(modularInterface) + QDEL_NULL(modularInterface) if(connected_ai) set_connected_ai(null) - if(shell) + if(shell) //??? why would you give an ai radio keys? GLOB.available_ai_shells -= src else if(T && istype(radio) && istype(radio.keyslot)) radio.keyslot.forceMove(T) radio.keyslot = null - qdel(wires) - qdel(module) - qdel(eye_lights) - wires = null - module = null - eye_lights = null + QDEL_NULL(wires) + QDEL_NULL(module) + QDEL_NULL(eye_lights) + QDEL_NULL(inv1) + QDEL_NULL(inv2) + QDEL_NULL(inv3) cell = null return ..() +// /mob/living/silicon/robot/Topic(href, href_list) +// . = ..() +// //Show alerts window if user clicked on "Show alerts" in chat +// if (href_list["showalerts"]) +// robot_alerts() + /mob/living/silicon/robot/proc/pick_module() if(module.type != /obj/item/robot_module) return @@ -136,7 +150,7 @@ if(BORG_SEC_AVAILABLE) modulelist["Security"] = /obj/item/robot_module/security - var/input_module = input("Please, select a module!", "Robot", null, null) as null|anything in modulelist + var/input_module = input("Please, select a module!", "Robot", null, null) as null|anything in sortList(modulelist) if(!input_module || module.type != /obj/item/robot_module) return @@ -151,9 +165,11 @@ var/changed_name = "" if(custom_name) changed_name = custom_name - if(changed_name == "" && C && C.prefs.custom_names["cyborg"] != DEFAULT_CYBORG_NAME) - if(apply_pref_name("cyborg", C)) - return //built in camera handled in proc + // if(SSticker.anonymousnames) //only robotic renames will allow for anything other than the anonymous one + // changed_name = anonymous_ai_name(is_ai = FALSE) + if(!changed_name && C && C.prefs.custom_names["cyborg"] != DEFAULT_CYBORG_NAME) + apply_pref_name("cyborg", C) + return //built in camera handled in proc if(!changed_name) changed_name = get_standard_name() @@ -262,7 +278,7 @@ C = O L[A.name] = list(A, (C) ? C : O, list(alarmsource)) queueAlarm(text("--- [class] alarm detected in [A.name]!"), class) - return 1 + return TRUE /mob/living/silicon/robot/cancelAlarm(class, area/A, obj/origin) var/list/L = alarms[class] @@ -281,6 +297,8 @@ return !cleared /mob/living/silicon/robot/can_interact_with(atom/A) + if (A == modularInterface) + return TRUE //bypass for borg tablets if (low_power_mode) return FALSE var/turf/T0 = get_turf(src) @@ -289,11 +307,13 @@ return FALSE return ISINRANGE(T1.x, T0.x - interaction_range, T0.x + interaction_range) && ISINRANGE(T1.y, T0.y - interaction_range, T0.y + interaction_range) -/mob/living/silicon/robot/proc/attempt_welder_repair(obj/item/weldingtool/W, mob/user) - if (!getBruteLoss()) +/mob/living/silicon/robot/proc/attempt_welder_repair(obj/item/W, mob/user) + if(!W.tool_behaviour == TOOL_WELDER) + return + if(!getBruteLoss()) to_chat(user, "[src] is already in good condition!") return - if (!W.tool_start_check(user, amount=0)) //The welder has 1u of fuel consumed by it's afterattack, so we don't need to worry about taking any away. + if(!W.tool_start_check(user, amount=0)) //The welder has 1u of fuel consumed by it's afterattack, so we don't need to worry about taking any away. return user.DelayNextAction(CLICK_CD_MELEE) if(src == user) @@ -333,7 +353,7 @@ to_chat(user, "The wires seem fine, there's no need to fix them.") /mob/living/silicon/robot/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/weldingtool) && (user.a_intent != INTENT_HARM || user == src)) + if(W.tool_behaviour == TOOL_WELDER && (user.a_intent != INTENT_HARM || user == src)) INVOKE_ASYNC(src, .proc/attempt_welder_repair, W, user) return @@ -341,7 +361,7 @@ INVOKE_ASYNC(src, .proc/attempt_cable_repair, W, user) return - else if(istype(W, /obj/item/crowbar)) // crowbar means open or close the cover + else if(W.tool_behaviour == TOOL_CROWBAR) // crowbar means open or close the cover if(opened) to_chat(user, "You close the cover.") opened = 0 @@ -373,12 +393,12 @@ else to_chat(user, "You can't reach the wiring!") - else if(istype(W, /obj/item/screwdriver) && opened && !cell) // haxing + else if(W.tool_behaviour == TOOL_SCREWDRIVER && opened && !cell) // haxing wiresexposed = !wiresexposed to_chat(user, "The wires have been [wiresexposed ? "exposed" : "unexposed"]") update_icons() - else if(istype(W, /obj/item/screwdriver) && opened && cell) // radio + else if((W.tool_behaviour == TOOL_SCREWDRIVER) && opened && cell) // radio if(shell) to_chat(user, "You cannot seem to open the radio compartment") //Prevent AI radio key theft else if(radio) @@ -387,7 +407,7 @@ to_chat(user, "Unable to locate a radio!") update_icons() - else if(istype(W, /obj/item/wrench) && opened && !cell) //Deconstruction. The flashes break from the fall, to prevent this from being a ghetto reset module. + else if(W.tool_behaviour == TOOL_WRENCH && opened && !cell) //Deconstruction. The flashes break from the fall, to prevent this from being a ghetto reset module. if(!locked_down) to_chat(user, "[src]'s bolts spark! Maybe you should lock them down first!") spark_system.start() @@ -473,19 +493,6 @@ toner = tonermax qdel(W) to_chat(user, "You fill the toner level of [src] to its max capacity.") - - else if(istype(W, /obj/item/flashlight)) - if(!opened) - to_chat(user, "You need to open the panel to repair the headlamp!") - else if(lamp_cooldown <= world.time) - to_chat(user, "The headlamp is already functional!") - else - if(!user.temporarilyRemoveItemFromInventory(W)) - to_chat(user, "[W] seems to be stuck to your hand. You'll have to find a different light.") - return - lamp_cooldown = 0 - qdel(W) - to_chat(user, "You replace the headlamp bulbs.") else return ..() @@ -518,47 +525,46 @@ /mob/living/silicon/robot/proc/allowed(mob/M) //check if it doesn't require any access at all if(check_access(null)) - return 1 + return TRUE if(ishuman(M)) var/mob/living/carbon/human/H = M //if they are holding or wearing a card that has access, that works if(check_access(H.get_active_held_item()) || check_access(H.wear_id)) - return 1 + return TRUE else if(ismonkey(M)) var/mob/living/carbon/monkey/george = M //they can only hold things :( if(isitem(george.get_active_held_item())) return check_access(george.get_active_held_item()) - return 0 + return FALSE /mob/living/silicon/robot/proc/check_access(obj/item/card/id/I) if(!istype(req_access, /list)) //something's very wrong - return 1 + return TRUE var/list/L = req_access if(!L.len) //no requirements - return 1 + return TRUE if(!istype(I, /obj/item/card/id) && isitem(I)) I = I.GetID() if(!I || !I.access) //not ID or no access - return 0 + return FALSE for(var/req in req_access) if(!(req in I.access)) //doesn't have this access - return 0 - return 1 + return FALSE + return TRUE /mob/living/silicon/robot/regenerate_icons() return update_icons() /mob/living/silicon/robot/proc/self_destruct() if(emagged) - if(mmi) - qdel(mmi) - explosion(src.loc,1,2,4,flame_range = 2) + QDEL_NULL(mmi) + explosion(loc,1,2,4,flame_range = 2) else - explosion(src.loc,-1,0,2) + explosion(loc,-1,0,2) gib() /mob/living/silicon/robot/proc/UnlinkSelf() @@ -597,6 +603,8 @@ clear_alert("locked") locked_down = state update_mobility() + logevent("System lockdown [locked_down?"triggered":"released"].") + /mob/living/silicon/robot/proc/SetEmagged(new_state) emagged = new_state @@ -607,6 +615,22 @@ else clear_alert("hacked") +/** + * Handles headlamp smashing + * + * When called (such as by the shadowperson lighteater's attack), this proc will break the borg's headlamp + * and then call toggle_headlamp to disable the light. It also plays a sound effect of glass breaking, and + * tells the borg what happened to its chat. Broken lights can be repaired by using a flashlight on the borg. + */ +/mob/living/silicon/robot/proc/smash_headlamp() + if(!lamp_functional) + return + lamp_functional = FALSE + playsound(src, 'sound/effects/glass_step.ogg', 50) + toggle_headlamp(TRUE) + to_chat(src, "Your headlamp is broken! You'll need a human to help replace it.") + + /mob/living/silicon/robot/verb/outputlaws() set category = "Robot Commands" set name = "State Laws" @@ -624,32 +648,40 @@ return //won't work if dead set_autosay() -/mob/living/silicon/robot/proc/control_headlamp() - if(stat || lamp_cooldown > world.time || low_power_mode) - to_chat(src, "This function is currently offline.") +/** + * Handles headlamp toggling, disabling, and color setting. + * + * The initial if statment is a bit long, but the gist of it is that should the lamp be on AND the update_color + * arg be true, we should simply change the color of the lamp but not disable it. Otherwise, should the turn_off + * arg be true, the lamp already be enabled, any of the normal reasons the lamp would turn off happen, or the + * update_color arg be passed with the lamp not on, we should set the lamp off. The update_color arg is only + * ever true when this proc is called from the borg tablet, when the color selection feature is used. + * + * Arguments: + * * arg1 - turn_off, if enabled will force the lamp into an off state (rather than toggling it if possible) + * * arg2 - update_color, if enabled, will adjust the behavior of the proc to change the color of the light if it is already on. + */ +/mob/living/silicon/robot/proc/toggle_headlamp(turn_off = FALSE, update_color = FALSE) + //if both lamp is enabled AND the update_color flag is on, keep the lamp on. Otherwise, if anything listed is true, disable the lamp. + if(!(update_color && lamp_enabled) && (turn_off || lamp_enabled || update_color || !lamp_functional || stat || low_power_mode)) + set_light((lamp_functional && stat != DEAD && lamp_doom) ? lamp_intensity : 0, l_color = COLOR_RED) + // set_light_on(lamp_functional && stat != DEAD && lamp_doom) //If the lamp isn't broken and borg isn't dead, doomsday borgs cannot disable their light fully. + // set_light_color(COLOR_RED) //This should only matter for doomsday borgs, as any other time the lamp will be off and the color not seen + // set_light_range(1) //Again, like above, this only takes effect when the light is forced on by doomsday mode. + lamp_enabled = FALSE + lampButton.update_icon() + update_icons() return - -//Some sort of magical "modulo" thing which somehow increments lamp power by 2, until it hits the max and resets to 0. - lamp_intensity = (lamp_intensity+2) % (lamp_max+2) - to_chat(src, "[lamp_intensity ? "Headlamp power set to Level [lamp_intensity/2]" : "Headlamp disabled."]") - update_headlamp() - -/mob/living/silicon/robot/proc/update_headlamp(var/turn_off = 0, var/cooldown = 100) - set_light(0) - - if(lamp_intensity && (turn_off || stat || low_power_mode)) - to_chat(src, "Your headlamp has been deactivated.") - lamp_intensity = 0 - lamp_cooldown = world.time + cooldown - else - set_light(lamp_intensity) - - if(lamp_button) - lamp_button.icon_state = "lamp[lamp_intensity]" - + set_light(lamp_intensity, l_color = (lamp_doom? COLOR_RED : lamp_color)) + // set_light_range(lamp_intensity) + // set_light_color(lamp_doom? COLOR_RED : lamp_color) //Red for doomsday killborgs, borg's choice otherwise + // set_light_on(TRUE) + lamp_enabled = TRUE + lampButton.update_icon() update_icons() /mob/living/silicon/robot/proc/deconstruct() + // SEND_SIGNAL(src, COMSIG_BORG_SAFE_DECONSTRUCT) var/turf/T = get_turf(src) if (robot_suit) robot_suit.forceMove(T) @@ -659,7 +691,7 @@ robot_suit.r_leg = null new /obj/item/stack/cable_coil(T, robot_suit.chest.wired) robot_suit.chest.forceMove(T) - robot_suit.chest.wired = 0 + robot_suit.chest.wired = FALSE robot_suit.chest = null robot_suit.l_arm.forceMove(T) robot_suit.l_arm = null @@ -692,8 +724,12 @@ cell = null qdel(src) +///This is the subtype that gets created by robot suits. It's needed so that those kind of borgs don't have a useless cell in them +/mob/living/silicon/robot/nocell + cell = null + /mob/living/silicon/robot/modules - var/set_module = null + var/set_module = /obj/item/robot_module /mob/living/silicon/robot/modules/Initialize() . = ..() @@ -733,14 +769,20 @@ Your cyborg LMG will slowly produce ammunition from your power supply, and your operative pinpointer will find and locate fellow nuclear operatives. \ Help the operatives secure the disk at all costs!
    " set_module = /obj/item/robot_module/syndicate + cell = /obj/item/stock_parts/cell/hyper + // radio = /obj/item/radio/borg/syndicate /mob/living/silicon/robot/modules/syndicate/Initialize() . = ..() - cell = new /obj/item/stock_parts/cell/hyper(src, 25000) radio = new /obj/item/radio/borg/syndicate(src) laws = new /datum/ai_laws/syndicate_override() addtimer(CALLBACK(src, .proc/show_playstyle), 5) +/mob/living/silicon/robot/modules/syndicate/create_modularInterface() + if(!modularInterface) + modularInterface = new /obj/item/modular_computer/tablet/integrated/syndicate(src) + return ..() + /mob/living/silicon/robot/modules/syndicate/proc/show_playstyle() if(playstyle_string) to_chat(src, playstyle_string) @@ -795,21 +837,32 @@ /mob/living/silicon/robot/updatehealth() ..() - if(health < maxHealth*0.5) //Gradual break down of modules as more damage is sustained - if(uneq_module(held_items[3])) - playsound(loc, 'sound/machines/warning-buzzer.ogg', 50, 1, 1) - audible_message("[src] sounds an alarm! \"SYSTEM ERROR: Module 3 OFFLINE.\"") - to_chat(src, "SYSTEM ERROR: Module 3 OFFLINE.") - if(health < 0) - if(uneq_module(held_items[2])) - audible_message("[src] sounds an alarm! \"SYSTEM ERROR: Module 2 OFFLINE.\"") - to_chat(src, "SYSTEM ERROR: Module 2 OFFLINE.") - playsound(loc, 'sound/machines/warning-buzzer.ogg', 60, 1, 1) - if(health < -maxHealth*0.5) - if(uneq_module(held_items[1])) - audible_message("[src] sounds an alarm! \"CRITICAL ERROR: All modules OFFLINE.\"") - to_chat(src, "CRITICAL ERROR: All modules OFFLINE.") - playsound(loc, 'sound/machines/warning-buzzer.ogg', 75, 1, 1) + // if(!module.breakable_modules) + // return + + /// the current percent health of the robot (-1 to 1) + var/percent_hp = health/maxHealth + if(health <= previous_health) //if change in health is negative (we're losing hp) + if(percent_hp <= 0.5) + break_cyborg_slot(3) + + if(percent_hp <= 0) + break_cyborg_slot(2) + + if(percent_hp <= -0.5) + break_cyborg_slot(1) + + else //if change in health is positive (we're gaining hp) + if(percent_hp >= 0.5) + repair_cyborg_slot(3) + + if(percent_hp >= 0) + repair_cyborg_slot(2) + + if(percent_hp >= -0.5) + repair_cyborg_slot(1) + + previous_health = health /mob/living/silicon/robot/update_sight() if(!client) @@ -832,7 +885,7 @@ if(sight_mode & BORGMESON) sight |= SEE_TURFS - lighting_alpha = LIGHTING_PLANE_ALPHA_INVISIBLE + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE see_in_dark = 1 if(sight_mode & BORGMATERIAL) @@ -847,6 +900,7 @@ if(sight_mode & BORGTHERM) sight |= SEE_MOBS + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE see_invisible = min(see_invisible, SEE_INVISIBLE_LIVING) see_in_dark = 8 @@ -860,34 +914,27 @@ if(stat != DEAD) if(health <= -maxHealth) //die only once death() + toggle_headlamp(1) return - if(IsUnconscious() || IsStun() || IsParalyzed() || getOxyLoss() > maxHealth*0.5) - if(stat == CONSCIOUS) - stat = UNCONSCIOUS - if(!eye_blind) - blind_eyes(1) - update_mobility() - update_headlamp() + if(IsUnconscious() || IsStun() || IsKnockdown() || IsParalyzed() || getOxyLoss() > maxHealth * 0.5) + stat = UNCONSCIOUS else - if(stat == UNCONSCIOUS) - stat = CONSCIOUS - adjust_blindness(-1) - update_mobility() - update_headlamp() + stat = CONSCIOUS + update_mobility() diag_hud_set_status() diag_hud_set_health() diag_hud_set_aishell() update_health_hud() -/mob/living/silicon/robot/revive(full_heal = 0, admin_revive = 0) +/mob/living/silicon/robot/revive(full_heal = FALSE, admin_revive = FALSE) if(..()) //successfully ressuscitated from death if(!QDELETED(builtInCamera) && !wires.is_cut(WIRE_CAMERA)) builtInCamera.toggle_cam(src,0) - update_headlamp() if(admin_revive) locked = TRUE notify_ai(NEW_BORG) - . = 1 + . = TRUE + toggle_headlamp(FALSE, TRUE) //This will reenable borg headlamps if doomsday is currently going on still. /mob/living/silicon/robot/fully_replace_character_name(oldname, newname) ..() @@ -899,6 +946,7 @@ /mob/living/silicon/robot/proc/ResetModule() + // SEND_SIGNAL(src, COMSIG_BORG_SAFE_DECONSTRUCT) uneq_all() shown_robot_modules = FALSE if(hud_used) @@ -908,6 +956,7 @@ resize = 0.5 hasExpanded = FALSE update_transform() + logevent("Chassis configuration has been reset.") module.transform_to(/obj/item/robot_module) // Remove upgrades. @@ -933,7 +982,8 @@ designation = module.name if(hands) hands.icon_state = module.moduleselect_icon - hands.icon = (module.moduleselect_alternate_icon ? module.moduleselect_alternate_icon : initial(hands.icon)) //CITADEL CHANGE - allows module select icons to use a different icon file + //CITADEL CHANGE - allows module select icons to use a different icon file + hands.icon = (module.moduleselect_alternate_icon ? module.moduleselect_alternate_icon : initial(hands.icon)) if(module.can_be_pushed) status_flags |= CANPUSH else @@ -947,7 +997,7 @@ hat_offset = module.hat_offset magpulse = module.magpulsing - updatename() + INVOKE_ASYNC(src, .proc/updatename) /mob/living/silicon/robot/proc/place_on_head(obj/item/new_hat) @@ -957,12 +1007,68 @@ new_hat.forceMove(src) update_icons() -/mob/living/silicon/robot/proc/make_shell(var/obj/item/borg/upgrade/ai/board) +/** + *Checking Exited() to detect if a hat gets up and walks off. + *Drones and pAIs might do this, after all. +*/ +/mob/living/silicon/robot/Exited(atom/A) + if(hat && hat == A) + hat = null + if(!QDELETED(src)) //Don't update icons if we are deleted. + update_icons() + return ..() + +///Use this to add upgrades to robots. It'll register signals for when the upgrade is moved or deleted, if not single use. +/mob/living/silicon/robot/proc/add_to_upgrades(obj/item/borg/upgrade/new_upgrade, mob/user) + if(new_upgrade in upgrades) + return FALSE + if(!user.temporarilyRemoveItemFromInventory(new_upgrade)) //calling the upgrade's dropped() proc /before/ we add action buttons + return FALSE + if(!new_upgrade.action(src, user)) + to_chat(user, "Upgrade error.") + new_upgrade.forceMove(loc) //gets lost otherwise + return FALSE + to_chat(user, "You apply the upgrade to [src].") + to_chat(src, "----------------\nNew hardware detected...Identified as \"[new_upgrade]\"...Setup complete.\n----------------") + if(new_upgrade.one_use) + logevent("Firmware [new_upgrade] run successfully.") + qdel(new_upgrade) + return FALSE + upgrades += new_upgrade + new_upgrade.forceMove(src) + RegisterSignal(new_upgrade, COMSIG_MOVABLE_MOVED, .proc/remove_from_upgrades) + RegisterSignal(new_upgrade, COMSIG_PARENT_QDELETING, .proc/on_upgrade_deleted) + logevent("Hardware [new_upgrade] installed successfully.") + +///Called when an upgrade is moved outside the robot. So don't call this directly, use forceMove etc. +/mob/living/silicon/robot/proc/remove_from_upgrades(obj/item/borg/upgrade/old_upgrade) + SIGNAL_HANDLER + if(loc == src) + return + old_upgrade.deactivate(src) + upgrades -= old_upgrade + UnregisterSignal(old_upgrade, list(COMSIG_MOVABLE_MOVED, COMSIG_PARENT_QDELETING)) + +///Called when an applied upgrade is deleted. +/mob/living/silicon/robot/proc/on_upgrade_deleted(obj/item/borg/upgrade/old_upgrade) + SIGNAL_HANDLER + if(!QDELETED(src)) + old_upgrade.deactivate(src) + upgrades -= old_upgrade + UnregisterSignal(old_upgrade, list(COMSIG_MOVABLE_MOVED, COMSIG_PARENT_QDELETING)) + +/** + * make_shell: Makes an AI shell out of a cyborg unit + * + * Arguments: + * * board - B.O.R.I.S. module board used for transforming the cyborg into AI shell + */ +/mob/living/silicon/robot/proc/make_shell(obj/item/borg/upgrade/ai/board) if(!board) upgrades |= new /obj/item/borg/upgrade/ai(src) shell = TRUE braintype = "AI Shell" - name = "[designation] AI Shell [rand(100,999)]" + name = "Empty AI Shell-[ident]" real_name = name GLOB.available_ai_shells |= src if(!QDELETED(builtInCamera)) @@ -970,6 +1076,9 @@ diag_hud_set_aishell() notify_ai(AI_SHELL) +/** + * revert_shell: Reverts AI shell back into a normal cyborg unit + */ /mob/living/silicon/robot/proc/revert_shell() if(!shell) return @@ -979,14 +1088,20 @@ qdel(boris) shell = FALSE GLOB.available_ai_shells -= src - name = "Unformatted Cyborg [rand(100,999)]" + name = "Unformatted Cyborg-[ident]" real_name = name if(!QDELETED(builtInCamera)) builtInCamera.c_tag = real_name diag_hud_set_aishell() -/mob/living/silicon/robot/proc/deploy_init(var/mob/living/silicon/ai/AI) - real_name = "[AI.real_name] shell [rand(100, 999)] - [designation]" //Randomizing the name so it shows up separately in the shells list +/** + * deploy_init: Deploys AI unit into AI shell + * + * Arguments: + * * AI - AI unit that initiated the deployment into the AI shell + */ +/mob/living/silicon/robot/proc/deploy_init(mob/living/silicon/ai/AI) + real_name = "[AI.real_name] [designation] Shell-[ident]" name = real_name if(!QDELETED(builtInCamera)) builtInCamera.c_tag = real_name //update the camera name too @@ -1067,10 +1182,10 @@ mainframe.diag_hud_set_deployed() if(mainframe.laws) mainframe.laws.show_laws(mainframe) //Always remind the AI when switching + if(mainframe.eyeobj) + mainframe.eyeobj.setLoc(loc) mainframe = null - - /mob/living/silicon/robot/attack_ai(mob/user) if(shell && (!connected_ai || connected_ai == user)) var/mob/living/silicon/ai/AI = user @@ -1078,6 +1193,7 @@ /mob/living/silicon/robot/shell shell = TRUE + cell = null /mob/living/silicon/robot/MouseDrop_T(mob/living/M, mob/living/user) . = ..() @@ -1088,20 +1204,19 @@ if(!is_type_in_typecache(M, can_ride_typecache)) M.visible_message("[M] really can't seem to mount [src]...") return + var/datum/component/riding/riding_datum = LoadComponent(/datum/component/riding/cyborg) if(buckled_mobs) if(buckled_mobs.len >= max_buckled_mobs) return if(M in buckled_mobs) return - if(stat) + + if(stat || incapacitated()) return - if(incapacitated()) + if(module && !module.allow_riding) + M.visible_message("Unfortunately, [M] just can't seem to hold onto [src]!") return - if(module) - if(!module.allow_riding) - M.visible_message("Unfortunately, [M] just can't seem to hold onto [src]!") - return if(iscarbon(M) && !M.incapacitated() && !riding_datum.equip_buckle_inhands(M, 1)) if(M.get_num_arms() <= 0) M.visible_message("[M] can't climb onto [src] because [M.p_they()] don't have any usable arms!") @@ -1118,17 +1233,25 @@ riding_datum.restore_position(user) . = ..(user) +/mob/living/silicon/robot/resist() + . = ..() + if(!has_buckled_mobs()) + return + for(var/i in buckled_mobs) + var/mob/unbuckle_me_now = i + unbuckle_mob(unbuckle_me_now, FALSE) + /mob/living/silicon/robot/proc/TryConnectToAI() set_connected_ai(select_active_ai_with_fewest_borgs(z)) if(connected_ai) lawsync() - lawupdate = 1 + lawupdate = TRUE return TRUE picturesync() return FALSE /mob/living/silicon/robot/proc/picturesync() - if(connected_ai && connected_ai.aicamera && aicamera) + if(connected_ai?.aicamera && aicamera) for(var/i in aicamera.stored) connected_ai.aicamera.stored[i] = TRUE for(var/i in connected_ai.aicamera.stored) @@ -1136,12 +1259,11 @@ /mob/living/silicon/robot/proc/charge(datum/source, amount, repairs) if(module) - var/coeff = amount * 0.005 - module.respawn_consumable(src, coeff) - if(repairs) - heal_bodypart_damage(repairs, repairs - 1) + module.respawn_consumable(src, amount * 0.005) if(cell) cell.charge = min(cell.charge + amount, cell.maxcharge) + if(repairs) + heal_bodypart_damage(repairs, repairs - 1) /mob/living/silicon/robot/proc/rest_style() set name = "Switch Rest Style" @@ -1176,5 +1298,31 @@ if(.) var/mob/living/silicon/ai/old_ai = . old_ai.connected_robots -= src + lamp_doom = FALSE if(connected_ai) connected_ai.connected_robots |= src + lamp_doom = connected_ai.doomsday_device ? TRUE : FALSE + toggle_headlamp(FALSE, TRUE) + +/** + * Records an IC event log entry in the cyborg's internal tablet. + * + * Creates an entry in the borglog list of the cyborg's internal tablet, listing the current + * in-game time followed by the message given. These logs can be seen by the cyborg in their + * BorgUI tablet app. By design, logging fails if the cyborg is dead. + * + * Arguments: + * arg1: a string containing the message to log. + */ +/mob/living/silicon/robot/proc/logevent(string = "") + if(!string) + return + if(stat == DEAD) //Dead borgs log no longer + return + if(!modularInterface) + stack_trace("Cyborg [src] ( [type] ) was somehow missing their integrated tablet. Please make a bug report.") + create_modularInterface() + modularInterface.borglog += "[STATION_TIME_TIMESTAMP("hh:mm:ss", world.time)] - [string]" + var/datum/computer_file/program/robotact/program = modularInterface.get_robotact() + if(program) + program.force_full_update() diff --git a/code/modules/mob/living/silicon/robot/robot_defense.dm b/code/modules/mob/living/silicon/robot/robot_defense.dm index 0ed380040b..36f291bf36 100644 --- a/code/modules/mob/living/silicon/robot/robot_defense.dm +++ b/code/modules/mob/living/silicon/robot/robot_defense.dm @@ -1,5 +1,11 @@ +GLOBAL_LIST_INIT(blacklisted_borg_hats, typecacheof(list( //Hats that don't really work on borgos + /obj/item/clothing/head/helmet/space, + /obj/item/clothing/head/welding, + /obj/item/clothing/head/chameleon/broken \ + ))) + /mob/living/silicon/robot/attackby(obj/item/I, mob/living/user) - if(hat_offset != INFINITY && user.a_intent == INTENT_HELP && is_type_in_typecache(I, equippable_hats)) + if(hat_offset != INFINITY && user.a_intent == INTENT_HELP && is_type_in_typecache(I, GLOB.blacklisted_borg_hats)) if(!(I.slot_flags & ITEM_SLOT_HEAD)) to_chat(user, "You can't quite fit [I] onto [src]'s head.") return diff --git a/code/modules/mob/living/silicon/robot/robot_defines.dm b/code/modules/mob/living/silicon/robot/robot_defines.dm index 4f7ae41969..f44552f1ab 100644 --- a/code/modules/mob/living/silicon/robot/robot_defines.dm +++ b/code/modules/mob/living/silicon/robot/robot_defines.dm @@ -1,15 +1,16 @@ /mob/living/silicon/robot + maxHealth = 100 + health = 100 designation = "Default" //used for displaying the prefix & getting the current module of cyborg has_limbs = TRUE hud_type = /datum/hud/robot + // radio = /obj/item/radio/borg + blocks_emissive = EMISSIVE_BLOCK_UNIQUE - - maxHealth = 100 - health = 100 - - combat_flags = COMBAT_FLAGS_DEFAULT + // light_system = MOVABLE_LIGHT_DIRECTIONAL + var/light_on = FALSE var/custom_name = "" var/braintype = "Cyborg" @@ -21,6 +22,8 @@ var/mob/living/silicon/ai/mainframe = null var/datum/action/innate/undeployment/undeployment_action = new + /// the last health before updating - to check net change in health + var/previous_health //Hud stuff var/obj/screen/inv1 = null @@ -38,16 +41,20 @@ var/obj/item/module_active = null held_items = list(null, null, null) //we use held_items for the module holding, because that makes sense to do! + /// For checking which modules are disabled or not. + var/disabled_modules + var/mutable_appearance/eye_lights var/mob/living/silicon/ai/connected_ai = null - var/obj/item/stock_parts/cell/cell = null + var/obj/item/stock_parts/cell/cell = /obj/item/stock_parts/cell/high ///If this is a path, this gets created as an object in Initialize. - var/opened = 0 + var/opened = FALSE var/emagged = FALSE var/emag_cooldown = 0 - var/wiresexposed = 0 + var/wiresexposed = FALSE + /// Random serial number generated for each cyborg upon its initialization var/ident = 0 var/locked = TRUE var/list/req_access = list(ACCESS_ROBOTICS) @@ -64,57 +71,52 @@ var/datum/effect_system/spark_spread/spark_system // So they can initialize sparks whenever/N var/lawupdate = 1 //Cyborgs will sync their laws with their AI by default - var/scrambledcodes = 0 // Used to determine if a borg shows up on the robotics console. Setting to one hides them. - var/locked_down //Boolean of whether the borg is locked down or not + var/scrambledcodes = FALSE // Used to determine if a borg shows up on the robotics console. Setting to TRUE hides them. + var/locked_down = FALSE //Boolean of whether the borg is locked down or not var/toner = 0 var/tonermax = 40 - var/lamp_max = 10 //Maximum brightness of a borg lamp. Set as a var for easy adjusting. - var/lamp_intensity = 0 //Luminosity of the headlamp. 0 is off. Higher settings than the minimum require power. - light_color = "#FFCC66" - light_power = 0.8 - var/lamp_cooldown = 0 //Flag for if the lamp is on cooldown after being forcibly disabled. + ///If the lamp isn't broken. + var/lamp_functional = TRUE + ///If the lamp is turned on + var/lamp_enabled = FALSE + ///Set lamp color + var/lamp_color = "#FFCC66" //COLOR_WHITE + ///Set to true if a doomsday event is locking our lamp to on and RED + var/lamp_doom = FALSE + ///Lamp brightness. Starts at 3, but can be 1 - 5. + var/lamp_intensity = 3 + ///Lamp button reference + var/obj/screen/robot/lamp/lampButton var/sight_mode = 0 hud_possible = list(ANTAG_HUD, DIAG_STAT_HUD, DIAG_HUD, DIAG_BATT_HUD, DIAG_TRACK_HUD) + ///The reference to the built-in tablet that borgs carry. + var/obj/item/modular_computer/tablet/integrated/modularInterface + var/obj/screen/robot/modPC/interfaceButton + var/list/upgrades = list() var/hasExpanded = FALSE var/obj/item/hat var/hat_offset = -3 - var/list/equippable_hats = list(/obj/item/clothing/head/caphat, - /obj/item/clothing/head/hardhat, - /obj/item/clothing/head/centhat, - /obj/item/clothing/head/HoS, - /obj/item/clothing/head/beret, - /obj/item/clothing/head/kitty, - /obj/item/clothing/head/hopcap, - /obj/item/clothing/head/wizard, - /obj/item/clothing/head/nursehat, - /obj/item/clothing/head/sombrero, - /obj/item/clothing/head/helmet/chaplain/witchunter_hat, - /obj/item/clothing/head/soft/, //All baseball caps - /obj/item/clothing/head/that, //top hat - /obj/item/clothing/head/collectable/tophat, //Not sure where this one is found, but it looks the same so might as well include - /obj/item/clothing/mask/bandana/, //All bandanas (which only work in hat mode) - /obj/item/clothing/head/fedora, - /obj/item/clothing/head/beanie/, //All beanies - /obj/item/clothing/ears/headphones, - /obj/item/clothing/head/helmet/skull, - /obj/item/clothing/head/crown/fancy) can_buckle = TRUE buckle_lying = FALSE + /// What types of mobs are allowed to ride/buckle to this mob var/static/list/can_ride_typecache = typecacheof(/mob/living/carbon/human) + // cit specific vars // var/sitting = 0 var/bellyup = 0 var/dogborg = FALSE var/cansprint = 1 + combat_flags = COMBAT_FLAGS_DEFAULT + var/orebox = null //doggie borg stuff. diff --git a/code/modules/mob/living/silicon/robot/robot_modules.dm b/code/modules/mob/living/silicon/robot/robot_modules.dm index 7c4125fbc6..0c3ba87f5f 100644 --- a/code/modules/mob/living/silicon/robot/robot_modules.dm +++ b/code/modules/mob/living/silicon/robot/robot_modules.dm @@ -269,9 +269,10 @@ if(!prev_locked_down) R.SetLockdown(0) R.setDir(SOUTH) - R.anchored = FALSE + R.set_anchored(FALSE) R.mob_transforming = FALSE - R.update_headlamp() + R.updatehealth() + R.update_icons() R.notify_ai(NEW_MODULE) if(R.hud_used) R.hud_used.update_robot_modules_display() @@ -923,7 +924,8 @@ /obj/item/cyborg_clamp, /obj/item/stack/marker_beacon, /obj/item/destTagger, - /obj/item/stack/packageWrap) + /obj/item/stack/packageWrap, + /obj/item/card/id/miningborg) emag_modules = list(/obj/item/borg/stun) ratvar_modules = list( /obj/item/clockwork/slab/cyborg/miner, diff --git a/code/modules/mob/living/silicon/robot/robot_sprint.dm b/code/modules/mob/living/silicon/robot/robot_sprint.dm index dff0d9dd0d..80adfe80fd 100644 --- a/code/modules/mob/living/silicon/robot/robot_sprint.dm +++ b/code/modules/mob/living/silicon/robot/robot_sprint.dm @@ -1,4 +1,7 @@ /mob/living/silicon/robot/default_toggle_sprint(shutdown = FALSE) + if(!CONFIG_GET(flag/sprint_enabled)) + disable_intentional_sprint_mode() + return var/current = (combat_flags & COMBAT_FLAG_SPRINT_ACTIVE) if(current || shutdown || !cell || (cell.charge < 25) || !cansprint) disable_intentional_sprint_mode() diff --git a/code/modules/mob/living/silicon/silicon.dm b/code/modules/mob/living/silicon/silicon.dm index 944b909463..b2b27c7d70 100644 --- a/code/modules/mob/living/silicon/silicon.dm +++ b/code/modules/mob/living/silicon/silicon.dm @@ -1,6 +1,6 @@ /mob/living/silicon gender = NEUTER - silicon_privileges = PRIVILEDGES_SILICON + silicon_privileges = PRIVILEGES_SILICON verb_say = "states" verb_ask = "queries" verb_exclaim = "declares" diff --git a/code/modules/mob/living/simple_animal/bot/bot.dm b/code/modules/mob/living/simple_animal/bot/bot.dm index bb7c437ca7..a7fd9c5ac0 100644 --- a/code/modules/mob/living/simple_animal/bot/bot.dm +++ b/code/modules/mob/living/simple_animal/bot/bot.dm @@ -15,7 +15,7 @@ maxbodytemp = INFINITY minbodytemp = 0 blood_volume = 0 - silicon_privileges = PRIVILEDGES_BOT + silicon_privileges = PRIVILEGES_BOT sentience_type = SENTIENCE_ARTIFICIAL status_flags = NONE //no default canpush verb_say = "states" @@ -306,7 +306,7 @@ show_controls(user) /mob/living/simple_animal/bot/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/screwdriver)) + if(W.tool_behaviour == TOOL_SCREWDRIVER) if(!locked) open = !open to_chat(user, "The maintenance panel is now [open ? "opened" : "closed"].") @@ -335,7 +335,7 @@ user.visible_message("[user] uses [W] to pull [paicard] out of [bot_name]!","You pull [paicard] out of [bot_name] with [W].") ejectpai(user) else - if(istype(W, /obj/item/weldingtool) && user.a_intent != INTENT_HARM) + if(W.tool_behaviour == TOOL_WELDER && user.a_intent != INTENT_HARM) if(health >= maxHealth) to_chat(user, "[src] does not need a repair!") return diff --git a/code/modules/mob/living/simple_animal/bot/cleanbot.dm b/code/modules/mob/living/simple_animal/bot/cleanbot.dm index e7c5644e26..a52e975f9a 100644 --- a/code/modules/mob/living/simple_animal/bot/cleanbot.dm +++ b/code/modules/mob/living/simple_animal/bot/cleanbot.dm @@ -14,8 +14,8 @@ model = "Cleanbot" bot_core_type = /obj/machinery/bot_core/cleanbot window_id = "autoclean" - window_name = "Automatic Station Cleaner v1.3" - pass_flags = PASSMOB + window_name = "Automatic Station Cleaner v1.4" + pass_flags = PASSMOB // | PASSFLAPS path_image_color = "#993299" weather_immunities = list("lava","ash") @@ -25,6 +25,7 @@ var/blood = 1 var/trash = 0 var/pests = 0 + var/drawn = 0 var/list/target_types var/obj/effect/decal/cleanable/target @@ -53,6 +54,9 @@ var/list/prefixes var/list/suffixes + var/ascended = FALSE // if we have all the top titles, grant achievements to living mobs that gaze upon our cleanbot god + + /mob/living/simple_animal/bot/cleanbot/proc/deputize(obj/item/W, mob/user) if(in_range(src, user)) to_chat(user, "You attach \the [W] to \the [src].") @@ -66,6 +70,8 @@ /mob/living/simple_animal/bot/cleanbot/proc/update_titles() var/working_title = "" + ascended = TRUE + for(var/pref in prefixes) for(var/title in pref) if(title in stolen_valor) @@ -73,6 +79,8 @@ if(title in officers) commissioned = TRUE break + else + ascended = FALSE // we didn't have the first entry in the list if we got here, so we're not achievement worthy yet working_title += chosen_name @@ -81,6 +89,8 @@ if(title in stolen_valor) working_title += " " + suf[title] break + else + ascended = FALSE name = working_title @@ -89,8 +99,12 @@ if(weapon) . += " Is that \a [weapon] taped to it...?" + if(ascended && user.stat == CONSCIOUS && user.client) + user.client.give_award(/datum/award/achievement/misc/cleanboss, user) + /mob/living/simple_animal/bot/cleanbot/Initialize() . = ..() + chosen_name = name get_targets() icon_state = "cleanbot[on]" @@ -98,7 +112,6 @@ var/datum/job/janitor/J = new/datum/job/janitor access_card.access += J.get_access() prev_access = access_card.access - stolen_valor = list() prefixes = list(command, security, engineering) @@ -123,7 +136,7 @@ /mob/living/simple_animal/bot/cleanbot/bot_reset() ..() - if(weapon && (emagged == 2)) + if(weapon && emagged == 2) weapon.force = weapon_orig_force ignore_list = list() //Allows the bot to clean targets it previously ignored due to being unreachable. target = null @@ -151,7 +164,7 @@ C.Knockdown(20) /mob/living/simple_animal/bot/cleanbot/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/card/id)||istype(W, /obj/item/pda)) + if(W.GetID()) if(bot_core.allowed(user) && !open && !emagged) locked = !locked to_chat(user, "You [ locked ? "lock" : "unlock"] \the [src] behaviour controls.") @@ -161,7 +174,7 @@ if(open) to_chat(user, "Please close the access panel before locking it.") else - to_chat(user, "The [src] doesn't seem to respect your authority.") + to_chat(user, "\The [src] doesn't seem to respect your authority.") else if(istype(W, /obj/item/kitchen/knife) && user.a_intent != INTENT_HARM) to_chat(user, "You start attaching \the [W] to \the [src]...") @@ -203,7 +216,8 @@ return ..() /mob/living/simple_animal/bot/cleanbot/emag_act(mob/user) - . = ..() + ..() + if(emagged == 2) if(weapon) weapon.force = weapon_orig_force @@ -259,6 +273,9 @@ if(!target && trash) //Then for trash. target = scan(/obj/item/trash) + // if(!target && trash) //Search for dead mices. + // target = scan(/obj/item/food/deadmouse) + if(!target && auto_patrol) //Search for cleanables it can see. if(mode == BOT_IDLE || mode == BOT_START_PATROL) start_patrol() @@ -335,13 +352,18 @@ target_types += /mob/living/simple_animal/cockroach target_types += /mob/living/simple_animal/mouse + if(drawn) + target_types += /obj/effect/decal/cleanable/crayon + if(trash) target_types += /obj/item/trash target_types += /obj/item/reagent_containers/food/snacks/meat/slab/human target_types = typecacheof(target_types) -/mob/living/simple_animal/bot/cleanbot/UnarmedAttack(atom/A, proximity, intent = a_intent, flags = NONE) +/mob/living/simple_animal/bot/cleanbot/UnarmedAttack(atom/A) + // if(HAS_TRAIT(src, TRAIT_HANDS_BLOCKED)) + // return if(istype(A, /obj/effect/decal/cleanable)) anchored = TRUE icon_state = "cleanbot-c" @@ -361,8 +383,9 @@ icon_state = "cleanbot[on]" else if(istype(A, /obj/item) || istype(A, /obj/effect/decal/remains)) visible_message("[src] sprays hydrofluoric acid at [A]!") - playsound(src, 'sound/effects/spray2.ogg', 50, 1, -6) + playsound(src, 'sound/effects/spray2.ogg', 50, TRUE, -6) A.acid_act(75, 10) + target = null else if(istype(A, /mob/living/simple_animal/cockroach) || istype(A, /mob/living/simple_animal/mouse)) var/mob/living/simple_animal/M = target if(!M.stat) @@ -383,7 +406,7 @@ "FREED AT LEST FROM FILTHY PROGRAMMING.") say(phrase) victim.emote("scream") - playsound(src.loc, 'sound/effects/spray2.ogg', 50, 1, -6) + playsound(src.loc, 'sound/effects/spray2.ogg', 50, TRUE, -6) victim.acid_act(5, 100) else if(A == src) // Wets floors and spawns foam randomly if(prob(75)) @@ -412,10 +435,14 @@ do_sparks(3, TRUE, src) ..() +/mob/living/simple_animal/bot/cleanbot/medbay + name = "Scrubs, MD" + bot_core_type = /obj/machinery/bot_core/cleanbot/medbay + on = FALSE + /obj/machinery/bot_core/cleanbot req_one_access = list(ACCESS_JANITOR, ACCESS_ROBOTICS) - /mob/living/simple_animal/bot/cleanbot/get_controls(mob/user) var/dat dat += hack(user) @@ -424,9 +451,10 @@ Status: [on ? "On" : "Off"]
    Behaviour controls are [locked ? "locked" : "unlocked"]
    Maintenance panel panel is [open ? "opened" : "closed"]"}) - if(!locked || hasSiliconAccessInArea(user)|| IsAdminGhost(user)) + if(!locked || issilicon(user)|| IsAdminGhost(user)) dat += "
    Clean Blood: [blood ? "Yes" : "No"]" dat += "
    Clean Trash: [trash ? "Yes" : "No"]" + dat += "
    Clean Graffiti: [drawn ? "Yes" : "No"]" dat += "
    Exterminate Pests: [pests ? "Yes" : "No"]" dat += "

    Patrol Station: [auto_patrol ? "Yes" : "No"]" return dat @@ -442,5 +470,10 @@ Maintenance panel panel is [open ? "opened" : "closed"]"}) pests = !pests if("trash") trash = !trash + if("drawn") + drawn = !drawn get_targets() update_controls() + +/obj/machinery/bot_core/cleanbot/medbay + req_one_access = list(ACCESS_JANITOR, ACCESS_ROBOTICS, ACCESS_MEDICAL) diff --git a/code/modules/mob/living/simple_animal/bot/construction.dm b/code/modules/mob/living/simple_animal/bot/construction.dm index 5102c02ecf..fff1782154 100644 --- a/code/modules/mob/living/simple_animal/bot/construction.dm +++ b/code/modules/mob/living/simple_animal/bot/construction.dm @@ -100,7 +100,7 @@ build_step++ if(ASSEMBLY_FOURTH_STEP) - if(istype(W, /obj/item/weldingtool)) + if(W.tool_behaviour == TOOL_WELDER) if(W.use_tool(src, user, 0, volume=40) && build_step == ASSEMBLY_FOURTH_STEP) name = "shielded frame assembly" to_chat(user, "You weld the vest to [src].") @@ -180,7 +180,7 @@ build_step++ if(8) - if(istype(W, /obj/item/screwdriver)) + if(W.tool_behaviour == TOOL_SCREWDRIVER) to_chat(user, "You start attaching the gun to the frame...") if(W.use_tool(src, user, 40, volume=100) && build_step == 8) name = "armed [name]" @@ -386,13 +386,13 @@ var/atom/Tsec = drop_location() switch(build_step) if(ASSEMBLY_FIRST_STEP) - if(istype(I, /obj/item/weldingtool)) + if(I.tool_behaviour == TOOL_WELDER) if(I.use_tool(src, user, 0, volume=40)) add_overlay("hs_hole") to_chat(user, "You weld a hole in [src]!") build_step++ - else if(istype(I, /obj/item/screwdriver)) //deconstruct + else if(I.tool_behaviour == TOOL_SCREWDRIVER) //deconstruct new /obj/item/assembly/signaler(Tsec) new /obj/item/clothing/head/helmet/sec(Tsec) to_chat(user, "You disconnect the signaler from the helmet.") @@ -408,7 +408,7 @@ qdel(I) build_step++ - else if(istype(I, /obj/item/weldingtool)) //deconstruct + else if(I.tool_behaviour == TOOL_WELDER) //deconstruct if(I.use_tool(src, user, 0, volume=40)) cut_overlay("hs_hole") to_chat(user, "You weld the hole in [src] shut!") @@ -425,7 +425,7 @@ qdel(I) build_step++ - else if(istype(I, /obj/item/screwdriver)) //deconstruct + else if(I.tool_behaviour == TOOL_SCREWDRIVER) //deconstruct cut_overlay("hs_eye") new /obj/item/assembly/prox_sensor(Tsec) to_chat(user, "You detach the proximity sensor from [src].") @@ -442,7 +442,7 @@ S.robot_arm = robot_arm qdel(I) qdel(src) - if(istype(I, /obj/item/wrench)) + if(I.tool_behaviour == TOOL_WRENCH) to_chat(user, "You adjust [src]'s arm slots to mount extra weapons") build_step ++ return @@ -466,7 +466,7 @@ qdel(I) qdel(src) - else if(istype(I, /obj/item/screwdriver)) //deconstruct + else if(I.tool_behaviour == TOOL_SCREWDRIVER) //deconstruct cut_overlay("hs_arm") var/obj/item/bodypart/dropped_arm = new robot_arm(Tsec) robot_arm = null @@ -499,7 +499,7 @@ S.robot_arm = robot_arm qdel(I) qdel(src) - else if(istype(I, /obj/item/screwdriver)) //deconstruct + else if(I.tool_behaviour == TOOL_SCREWDRIVER) //deconstruct build_step-- icon_state = initial(icon_state) to_chat(user, "You unbolt [src]'s energy swords") diff --git a/code/modules/mob/living/simple_animal/bot/ed209bot.dm b/code/modules/mob/living/simple_animal/bot/ed209bot.dm index 8375d621d4..604a72b43e 100644 --- a/code/modules/mob/living/simple_animal/bot/ed209bot.dm +++ b/code/modules/mob/living/simple_animal/bot/ed209bot.dm @@ -184,9 +184,9 @@ Auto Patrol[]"}, /mob/living/simple_animal/bot/ed209/attackby(obj/item/W, mob/user, params) ..() - if(istype(W, /obj/item/weldingtool) && user.a_intent != INTENT_HARM) // Any intent but harm will heal, so we shouldn't get angry. + if(W.tool_behaviour == TOOL_WELDER && user.a_intent != INTENT_HARM) // Any intent but harm will heal, so we shouldn't get angry. return - if(!istype(W, /obj/item/screwdriver) && (!target)) // Added check for welding tool to fix #2432. Welding tool behavior is handled in superclass. + if(!W.tool_behaviour == TOOL_SCREWDRIVER && (!target)) // Added check for welding tool to fix #2432. Welding tool behavior is handled in superclass. if(W.force && W.damtype != STAMINA)//If force is non-zero and damage type isn't stamina. retaliate(user) if(lasercolor)//To make up for the fact that lasertag bots don't hunt diff --git a/code/modules/mob/living/simple_animal/bot/floorbot.dm b/code/modules/mob/living/simple_animal/bot/floorbot.dm index 0ba4023864..ff6941ec70 100644 --- a/code/modules/mob/living/simple_animal/bot/floorbot.dm +++ b/code/modules/mob/living/simple_animal/bot/floorbot.dm @@ -335,6 +335,11 @@ /mob/living/simple_animal/bot/floorbot/proc/repair(turf/target_turf) + if(check_bot_working(target_turf)) + add_to_ignore(target_turf) + target = null + playsound(src, 'sound/effects/whistlereset.ogg', 50, TRUE) + return if(isspaceturf(target_turf)) //Must be a hull breach or in line mode to continue. if(!is_hull_breach(target_turf) && !targetdirection) @@ -418,3 +423,14 @@ repair(A) else ..() + +/** + * Checks a given turf to see if another floorbot is there, working as well. + */ +/mob/living/simple_animal/bot/floorbot/proc/check_bot_working(turf/active_turf) + if(isturf(active_turf)) + for(var/mob/living/simple_animal/bot/floorbot/robot in active_turf) + if(robot.mode == BOT_REPAIRING) + return TRUE + return FALSE + diff --git a/code/modules/mob/living/simple_animal/bot/honkbot.dm b/code/modules/mob/living/simple_animal/bot/honkbot.dm index 0ee600ed1d..9625d2b084 100644 --- a/code/modules/mob/living/simple_animal/bot/honkbot.dm +++ b/code/modules/mob/living/simple_animal/bot/honkbot.dm @@ -120,9 +120,9 @@ Maintenance panel panel is [open ? "opened" : "closed"]"}, /mob/living/simple_animal/bot/honkbot/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/weldingtool) && user.a_intent != INTENT_HARM) + if(W.tool_behaviour == TOOL_WELDER && user.a_intent != INTENT_HARM) return - if(!istype(W, /obj/item/screwdriver) && (W.force) && (!target) && (W.damtype != STAMINA) ) // Check for welding tool to fix #2432. + if(!W.tool_behaviour == TOOL_SCREWDRIVER && (W.force) && (!target) && (W.damtype != STAMINA) ) // Check for welding tool to fix #2432. retaliate(user) addtimer(CALLBACK(src, .proc/react_buzz), 5) ..() diff --git a/code/modules/mob/living/simple_animal/bot/secbot.dm b/code/modules/mob/living/simple_animal/bot/secbot.dm index cf9698655b..8bb30a11a6 100644 --- a/code/modules/mob/living/simple_animal/bot/secbot.dm +++ b/code/modules/mob/living/simple_animal/bot/secbot.dm @@ -299,12 +299,12 @@ Auto Patrol: []"}, /mob/living/simple_animal/bot/secbot/attackby(obj/item/W, mob/user, params) ..() - if(istype(W, /obj/item/weldingtool) && user.a_intent != INTENT_HARM) // Any intent but harm will heal, so we shouldn't get angry. + if(W.tool_behaviour == TOOL_WELDER && user.a_intent != INTENT_HARM) // Any intent but harm will heal, so we shouldn't get angry. return if(istype(W, /obj/item/clothing/head)) attempt_place_on_head(user, W) return - if(!istype(W, /obj/item/screwdriver) && (W.force) && (!target) && (W.damtype != STAMINA) ) // Added check for welding tool to fix #2432. Welding tool behavior is handled in superclass. + if(!W.tool_behaviour == TOOL_SCREWDRIVER && (W.force) && (!target) && (W.damtype != STAMINA) ) // Added check for welding tool to fix #2432. Welding tool behavior is handled in superclass. retaliate(user) if(special_retaliate_after_attack(user)) return diff --git a/code/modules/mob/living/simple_animal/friendly/bumbles.dm b/code/modules/mob/living/simple_animal/friendly/bumbles.dm index 0debb7b98c..6ed6d03b68 100644 --- a/code/modules/mob/living/simple_animal/friendly/bumbles.dm +++ b/code/modules/mob/living/simple_animal/friendly/bumbles.dm @@ -20,7 +20,6 @@ density = FALSE movement_type = FLYING pass_flags = PASSTABLE | PASSGRILLE | PASSMOB - ventcrawler = VENTCRAWLER_ALWAYS mob_size = MOB_SIZE_TINY mob_biotypes = MOB_ORGANIC|MOB_BEAST gold_core_spawnable = FRIENDLY_SPAWN @@ -35,6 +34,7 @@ /mob/living/simple_animal/pet/bumbles/Initialize() . = ..() add_verb(src, /mob/living/proc/lay_down) + AddElement(/datum/element/ventcrawling, given_tier = VENTCRAWLER_ALWAYS) /mob/living/simple_animal/pet/bumbles/ComponentInitialize() . = ..() diff --git a/code/modules/mob/living/simple_animal/friendly/butterfly.dm b/code/modules/mob/living/simple_animal/friendly/butterfly.dm index bf4f45e283..1e6afd6044 100644 --- a/code/modules/mob/living/simple_animal/friendly/butterfly.dm +++ b/code/modules/mob/living/simple_animal/friendly/butterfly.dm @@ -20,7 +20,6 @@ density = FALSE movement_type = FLYING pass_flags = PASSTABLE | PASSGRILLE | PASSMOB - ventcrawler = VENTCRAWLER_ALWAYS mob_size = MOB_SIZE_TINY mob_biotypes = MOB_ORGANIC|MOB_BUG gold_core_spawnable = FRIENDLY_SPAWN @@ -33,6 +32,7 @@ . = ..() var/newcolor = rgb(rand(0, 255), rand(0, 255), rand(0, 255)) add_atom_colour(newcolor, FIXED_COLOUR_PRIORITY) + AddElement(/datum/element/ventcrawling, given_tier = VENTCRAWLER_ALWAYS) /mob/living/simple_animal/butterfly/bee_friendly() return TRUE //treaty signed at the Beeneeva convention diff --git a/code/modules/mob/living/simple_animal/friendly/cat.dm b/code/modules/mob/living/simple_animal/friendly/cat.dm index 71ae3b4c03..4594d27ecd 100644 --- a/code/modules/mob/living/simple_animal/friendly/cat.dm +++ b/code/modules/mob/living/simple_animal/friendly/cat.dm @@ -14,7 +14,6 @@ speak_chance = 1 turns_per_move = 5 see_in_dark = 6 - ventcrawler = VENTCRAWLER_ALWAYS pass_flags = PASSTABLE mob_size = MOB_SIZE_SMALL mob_biotypes = MOB_ORGANIC|MOB_BEAST @@ -40,6 +39,7 @@ /mob/living/simple_animal/pet/cat/Initialize() . = ..() add_verb(src, /mob/living/proc/lay_down) + AddElement(/datum/element/ventcrawling, given_tier = VENTCRAWLER_ALWAYS) /mob/living/simple_animal/pet/cat/ComponentInitialize() . = ..() diff --git a/code/modules/mob/living/simple_animal/friendly/cockroach.dm b/code/modules/mob/living/simple_animal/friendly/cockroach.dm index 384fa8146f..f65b2bab9b 100644 --- a/code/modules/mob/living/simple_animal/friendly/cockroach.dm +++ b/code/modules/mob/living/simple_animal/friendly/cockroach.dm @@ -22,7 +22,6 @@ response_harm_simple = "splat" speak_emote = list("chitters") density = FALSE - ventcrawler = VENTCRAWLER_ALWAYS mob_size = MOB_SIZE_TINY gold_core_spawnable = FRIENDLY_SPAWN verb_say = "chitters" @@ -32,28 +31,34 @@ var/squish_chance = 50 del_on_death = 1 +/mob/living/simple_animal/cockroach/Initialize() + . = ..() + AddElement(/datum/element/ventcrawling, given_tier = VENTCRAWLER_ALWAYS) + /mob/living/simple_animal/cockroach/death(gibbed) if(SSticker.mode && SSticker.mode.station_was_nuked) //If the nuke is going off, then cockroaches are invincible. Keeps the nuke from killing them, cause cockroaches are immune to nukes. return ..() -/mob/living/simple_animal/cockroach/Crossed(var/atom/movable/AM) - if(ismob(AM)) - if(isliving(AM)) - var/mob/living/A = AM - if(A.mob_size > MOB_SIZE_SMALL && !(A.movement_type & FLYING)) - if(prob(squish_chance)) - A.visible_message("[A] squashed [src].", "You squashed [src].") - adjustBruteLoss(1) //kills a normal cockroach - else - visible_message("[src] avoids getting crushed.") - else - if(isstructure(AM)) +/mob/living/simple_animal/cockroach/Crossed(atom/movable/AM) + . = ..() + if(isliving(AM)) + var/mob/living/A = AM + if(A.mob_size > MOB_SIZE_SMALL && !(A.movement_type & FLYING)) + if(HAS_TRAIT(A, TRAIT_PACIFISM)) + A.visible_message("[A] carefully steps over [src].", "You carefully step over [src] to avoid hurting it.") + return if(prob(squish_chance)) - AM.visible_message("[src] was crushed under [AM].") - adjustBruteLoss(1) + A.visible_message("[A] squashed [src].", "You squashed [src].") + adjustBruteLoss(1) //kills a normal cockroach else visible_message("[src] avoids getting crushed.") + else if(isstructure(AM)) + if(prob(squish_chance)) + AM.visible_message("[src] is crushed under [AM].") + adjustBruteLoss(1) + else + visible_message("[src] avoids getting crushed.") /mob/living/simple_animal/cockroach/ex_act() //Explosions are a terrible way to handle a cockroach. return diff --git a/code/modules/mob/living/simple_animal/friendly/crab.dm b/code/modules/mob/living/simple_animal/friendly/crab.dm index 7f3693f622..addbd493a5 100644 --- a/code/modules/mob/living/simple_animal/friendly/crab.dm +++ b/code/modules/mob/living/simple_animal/friendly/crab.dm @@ -21,12 +21,15 @@ stop_automated_movement = 1 friendly_verb_continuous = "pinches" friendly_verb_simple = "pinch" - ventcrawler = VENTCRAWLER_ALWAYS mob_size = MOB_SIZE_TINY var/obj/item/inventory_head var/obj/item/inventory_mask gold_core_spawnable = FRIENDLY_SPAWN +/mob/living/simple_animal/crab/Initialize() + . = ..() + AddElement(/datum/element/ventcrawling, given_tier = VENTCRAWLER_ALWAYS) + /mob/living/simple_animal/crab/BiologicalLife(seconds, times_fired) if(!(. = ..())) return diff --git a/code/modules/mob/living/simple_animal/friendly/dog.dm b/code/modules/mob/living/simple_animal/friendly/dog.dm index 0584995583..49581b91a7 100644 --- a/code/modules/mob/living/simple_animal/friendly/dog.dm +++ b/code/modules/mob/living/simple_animal/friendly/dog.dm @@ -263,7 +263,7 @@ /mob/living/simple_animal/pet/dog/corgi/proc/place_on_head(obj/item/item_to_add, mob/user) if(istype(item_to_add, /obj/item/grenade/plastic)) // last thing he ever wears, I guess - item_to_add.afterattack(src,user,1) + INVOKE_ASYNC(item_to_add, /obj/item.proc/afterattack, src, user, 1) return if(inventory_head) @@ -271,13 +271,15 @@ to_chat(user, "You can't put more than one hat on [src]!") return if(!item_to_add) - user.visible_message("[user] pets [src].","You rest your hand on [src]'s head for a moment.") + user.visible_message("[user] pets [src].", "You rest your hand on [src]'s head for a moment.") + if(flags_1 & HOLOGRAM_1) + return SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, src, /datum/mood_event/pet_animal, src) return if(user && !user.temporarilyRemoveItemFromInventory(item_to_add)) to_chat(user, "\The [item_to_add] is stuck to your hand, you cannot put it on [src]'s head!") - return 0 + return var/valid = FALSE if(ispath(item_to_add.dog_fashion, /datum/dog_fashion/head)) @@ -287,11 +289,11 @@ if(valid) if(health <= 0) - to_chat(user, "There is merely a dull, lifeless look in [real_name]'s eyes as you put the [item_to_add] on [p_them()].") + to_chat(user, "There is merely a dull, lifeless look in [real_name]'s eyes as you put the [item_to_add] on [p_them()].") else if(user) - user.visible_message("[user] puts [item_to_add] on [real_name]'s head. [src] looks at [user] and barks once.", - "You put [item_to_add] on [real_name]'s head. [src] gives you a peculiar look, then wags [p_their()] tail once and barks.", - "You hear a friendly-sounding bark.") + user.visible_message("[user] puts [item_to_add] on [real_name]'s head. [src] looks at [user] and barks once.", + "You put [item_to_add] on [real_name]'s head. [src] gives you a peculiar look, then wags [p_their()] tail once and barks.", + "You hear a friendly-sounding bark.") item_to_add.forceMove(src) src.inventory_head = item_to_add update_corgi_fluff() @@ -361,7 +363,7 @@ icon_state = "old_corgi" icon_living = "old_corgi" icon_dead = "old_corgi_dead" - desc = "At a ripe old age of [record_age] Ian's not as spry as he used to be, but he'll always be the HoP's beloved corgi." //RIP + desc = "At a ripe old age of [record_age], Ian's not as spry as he used to be, but he'll always be the HoP's beloved corgi." //RIP turns_per_move = 20 RemoveElement(/datum/element/mob_holder, held_icon) AddElement(/datum/element/mob_holder, "old_corgi") diff --git a/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm b/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm index a8c0e7d207..ecd5db3ccf 100644 --- a/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm +++ b/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm @@ -28,7 +28,6 @@ unsuitable_atmos_damage = 0 wander = 0 speed = 0 - ventcrawler = VENTCRAWLER_ALWAYS healable = 0 density = FALSE pass_flags = PASSTABLE | PASSMOB @@ -41,7 +40,7 @@ bubble_icon = "machine" initial_language_holder = /datum/language_holder/drone mob_size = MOB_SIZE_SMALL - silicon_privileges = PRIVILEDGES_DRONE + silicon_privileges = PRIVILEGES_DRONE damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 0, CLONE = 0, STAMINA = 0, OXY = 0) hud_possible = list(DIAG_STAT_HUD, DIAG_HUD, ANTAG_HUD) unique_name = TRUE @@ -101,6 +100,8 @@ for(var/datum/atom_hud/data/diagnostic/diag_hud in GLOB.huds) diag_hud.add_to_hud(src) + AddElement(/datum/element/ventcrawling, given_tier = VENTCRAWLER_ALWAYS) + /mob/living/simple_animal/drone/ComponentInitialize() . = ..() if(can_be_held) diff --git a/code/modules/mob/living/simple_animal/friendly/drone/extra_drone_types.dm b/code/modules/mob/living/simple_animal/friendly/drone/extra_drone_types.dm index 55339a02fd..12b6447a02 100644 --- a/code/modules/mob/living/simple_animal/friendly/drone/extra_drone_types.dm +++ b/code/modules/mob/living/simple_animal/friendly/drone/extra_drone_types.dm @@ -114,7 +114,6 @@ harm_intent_damage = 5 density = TRUE speed = 1 - ventcrawler = VENTCRAWLER_NONE faction = list("neutral", "ratvar") speak_emote = list("clanks", "clinks", "clunks", "clangs") verb_ask = "requests" @@ -161,7 +160,7 @@ ..() /mob/living/simple_animal/drone/attackby(obj/item/I, mob/user) - if(istype(I, /obj/item/screwdriver) && stat == DEAD) + if(I.tool_behaviour == TOOL_SCREWDRIVER && stat == DEAD) try_reactivate(user) else ..() diff --git a/code/modules/mob/living/simple_animal/friendly/drone/interaction.dm b/code/modules/mob/living/simple_animal/friendly/drone/interaction.dm index 54184310a8..d9ea6f4a8a 100644 --- a/code/modules/mob/living/simple_animal/friendly/drone/interaction.dm +++ b/code/modules/mob/living/simple_animal/friendly/drone/interaction.dm @@ -55,7 +55,7 @@ /mob/living/simple_animal/drone/attackby(obj/item/I, mob/user) - if(istype(I, /obj/item/screwdriver) && stat != DEAD) + if(I.tool_behaviour == TOOL_SCREWDRIVER && stat != DEAD) if(health < maxHealth) to_chat(user, "You start to tighten loose screws on [src]...") if(I.use_tool(src, user, 80)) @@ -66,7 +66,7 @@ else to_chat(user, "[src]'s screws can't get any tighter!") return //This used to not exist and drones who repaired themselves also stabbed the shit out of themselves. - else if(istype(I, /obj/item/wrench) && user != src) //They aren't required to be hacked, because laws can change in other ways (i.e. admins) + else if(I.tool_behaviour == TOOL_WRENCH && user != src) //They aren't required to be hacked, because laws can change in other ways (i.e. admins) user.visible_message("[user] starts resetting [src]...", \ "You press down on [src]'s factory reset control...") if(I.use_tool(src, user, 50, volume=50)) @@ -110,7 +110,7 @@ to_chat(src, "Your onboard antivirus has initiated lockdown. Motor servos are impaired, ventilation access is denied, and your display reports that you are hacked to all nearby.") hacked = TRUE mind.special_role = "hacked drone" - ventcrawler = VENTCRAWLER_NONE //Again, balance + RemoveElement(/datum/element/ventcrawling, given_tier = VENTCRAWLER_ALWAYS) // balance speed = 1 //gotta go slow message_admins("[src] ([src.key]) became a hacked drone hellbent on [clockwork ? "serving Ratvar" : "destroying the station"]!") else @@ -125,7 +125,7 @@ to_chat(src, "Having been restored, your onboard antivirus reports the all-clear and you are able to perform all actions again.") hacked = FALSE mind.special_role = null - ventcrawler = initial(ventcrawler) + AddElement(/datum/element/ventcrawling, given_tier = VENTCRAWLER_ALWAYS) speed = initial(speed) if(is_servant_of_ratvar(src)) remove_servant_of_ratvar(src, TRUE) diff --git a/code/modules/mob/living/simple_animal/friendly/drone/inventory.dm b/code/modules/mob/living/simple_animal/friendly/drone/inventory.dm index 8034e3c5e5..5439bbf665 100644 --- a/code/modules/mob/living/simple_animal/friendly/drone/inventory.dm +++ b/code/modules/mob/living/simple_animal/friendly/drone/inventory.dm @@ -6,7 +6,7 @@ //Drone hands -/mob/living/simple_animal/drone/doUnEquip(obj/item/I, force) +/mob/living/simple_animal/drone/doUnEquip(obj/item/I, force, silent = FALSE) if(..()) update_inv_hands() if(I == head) diff --git a/code/modules/mob/living/simple_animal/friendly/farm_animals.dm b/code/modules/mob/living/simple_animal/friendly/farm_animals.dm index 51e7ee6c03..81925d922f 100644 --- a/code/modules/mob/living/simple_animal/friendly/farm_animals.dm +++ b/code/modules/mob/living/simple_animal/friendly/farm_animals.dm @@ -241,7 +241,6 @@ attack_verb_simple = "kick" health = 3 maxHealth = 3 - ventcrawler = VENTCRAWLER_ALWAYS var/amount_grown = 0 pass_flags = PASSTABLE | PASSGRILLE | PASSMOB mob_size = MOB_SIZE_TINY @@ -253,6 +252,7 @@ . = ..() pixel_x = rand(-6, 6) pixel_y = rand(0, 10) + AddElement(/datum/element/ventcrawling, given_tier = VENTCRAWLER_ALWAYS) /mob/living/simple_animal/chick/BiologicalLife(seconds, times_fired) if(!(. = ..())) @@ -296,7 +296,6 @@ attack_verb_simple = "kick" health = 15 maxHealth = 15 - ventcrawler = VENTCRAWLER_ALWAYS var/eggsleft = 0 var/eggsFertile = TRUE var/body_color @@ -322,6 +321,8 @@ pixel_y = rand(0, 10) ++chicken_count + AddElement(/datum/element/ventcrawling, given_tier = VENTCRAWLER_ALWAYS) + /mob/living/simple_animal/chicken/Destroy() --chicken_count return ..() @@ -392,7 +393,6 @@ attack_verb_simple = "kick" health = 25 maxHealth = 25 - ventcrawler = VENTCRAWLER_ALWAYS var/eggsleft = 0 var/eggsFertile = TRUE pass_flags = PASSTABLE | PASSMOB @@ -412,6 +412,8 @@ . = ..() ++kiwi_count + AddElement(/datum/element/ventcrawling, given_tier = VENTCRAWLER_ALWAYS) + /mob/living/simple_animal/kiwi/BiologicalLife(seconds, times_fired) if(!(. = ..())) return @@ -473,7 +475,6 @@ attack_verb_simple = "kick" health = 10 maxHealth = 10 - ventcrawler = VENTCRAWLER_ALWAYS var/amount_grown = 0 pass_flags = PASSTABLE | PASSGRILLE | PASSMOB mob_size = MOB_SIZE_TINY @@ -486,6 +487,8 @@ pixel_x = rand(-6, 6) pixel_y = rand(0, 10) + AddElement(/datum/element/ventcrawling, given_tier = VENTCRAWLER_ALWAYS) + /mob/living/simple_animal/babyKiwi/BiologicalLife(seconds, times_fired) if(!(. = ..())) return diff --git a/code/modules/mob/living/simple_animal/friendly/lizard.dm b/code/modules/mob/living/simple_animal/friendly/lizard.dm index b9ff99b677..fd0095bf87 100644 --- a/code/modules/mob/living/simple_animal/friendly/lizard.dm +++ b/code/modules/mob/living/simple_animal/friendly/lizard.dm @@ -18,7 +18,6 @@ response_disarm_simple = "shoo" response_harm_continuous = "stomps on" response_harm_simple = "stomp on" - ventcrawler = VENTCRAWLER_ALWAYS density = FALSE pass_flags = PASSTABLE | PASSMOB mob_size = MOB_SIZE_SMALL @@ -31,6 +30,7 @@ /mob/living/simple_animal/hostile/lizard/ComponentInitialize() . = ..() AddElement(/datum/element/mob_holder, worn_state = "lizard", inv_slots = ITEM_SLOT_HEAD) //you can hold lizards now. + AddElement(/datum/element/ventcrawling, given_tier = VENTCRAWLER_ALWAYS) /mob/living/simple_animal/hostile/lizard/CanAttack(atom/the_target)//Can we actually attack a possible target? if(see_invisible < the_target.invisibility)//Target's invisible to us, forget it diff --git a/code/modules/mob/living/simple_animal/friendly/mouse.dm b/code/modules/mob/living/simple_animal/friendly/mouse.dm index 51c903ee16..8348af43fa 100644 --- a/code/modules/mob/living/simple_animal/friendly/mouse.dm +++ b/code/modules/mob/living/simple_animal/friendly/mouse.dm @@ -22,7 +22,6 @@ response_harm_continuous = "splats" response_harm_simple = "splat" density = FALSE - ventcrawler = VENTCRAWLER_ALWAYS pass_flags = PASSTABLE | PASSGRILLE | PASSMOB mob_size = MOB_SIZE_TINY mob_biotypes = MOB_ORGANIC|MOB_BEAST @@ -40,6 +39,7 @@ icon_state = "mouse_[body_color]" icon_living = "mouse_[body_color]" icon_dead = "mouse_[body_color]_dead" + AddElement(/datum/element/ventcrawling, given_tier = VENTCRAWLER_ALWAYS) /mob/living/simple_animal/mouse/proc/splat() src.health = 0 diff --git a/code/modules/mob/living/simple_animal/friendly/possum.dm b/code/modules/mob/living/simple_animal/friendly/possum.dm index 71fdbd1465..5e7ed572e2 100644 --- a/code/modules/mob/living/simple_animal/friendly/possum.dm +++ b/code/modules/mob/living/simple_animal/friendly/possum.dm @@ -22,12 +22,15 @@ response_harm_continuous = "stamps on" response_harm_simple = "stamp" density = FALSE - ventcrawler = VENTCRAWLER_ALWAYS pass_flags = PASSTABLE | PASSMOB mob_size = MOB_SIZE_TINY mob_biotypes = MOB_ORGANIC|MOB_BEAST gold_core_spawnable = FRIENDLY_SPAWN +/mob/living/simple_animal/opossum/Initialize() + . = ..() + AddElement(/datum/element/ventcrawling, given_tier = VENTCRAWLER_ALWAYS) + /mob/living/simple_animal/opossum/poppy name = "Poppy the Safety Possum" desc = "Safety first!" diff --git a/code/modules/mob/living/simple_animal/friendly/snake.dm b/code/modules/mob/living/simple_animal/friendly/snake.dm index 4d20b927a6..61c735c7c1 100644 --- a/code/modules/mob/living/simple_animal/friendly/snake.dm +++ b/code/modules/mob/living/simple_animal/friendly/snake.dm @@ -30,7 +30,6 @@ response_harm_continuous = "steps on" response_harm_simple = "step on" faction = list("hostile") - ventcrawler = VENTCRAWLER_ALWAYS density = FALSE pass_flags = PASSTABLE | PASSMOB mob_size = MOB_SIZE_SMALL @@ -39,6 +38,9 @@ obj_damage = 0 environment_smash = ENVIRONMENT_SMASH_NONE +/mob/living/simple_animal/hostile/retaliate/poison/snake/Initialize() + . = ..() + AddElement(/datum/element/ventcrawling, given_tier = VENTCRAWLER_ALWAYS) /mob/living/simple_animal/hostile/retaliate/poison/snake/ListTargets(atom/the_target) . = oview(vision_range, targets_from) //get list of things in vision range diff --git a/code/modules/mob/living/simple_animal/guardian/types/dextrous.dm b/code/modules/mob/living/simple_animal/guardian/types/dextrous.dm index a1850fabca..a079507a63 100644 --- a/code/modules/mob/living/simple_animal/guardian/types/dextrous.dm +++ b/code/modules/mob/living/simple_animal/guardian/types/dextrous.dm @@ -41,7 +41,7 @@ ..() //lose items, then return //SLOT HANDLING BULLSHIT FOR INTERNAL STORAGE -/mob/living/simple_animal/hostile/guardian/dextrous/doUnEquip(obj/item/I, force) +/mob/living/simple_animal/hostile/guardian/dextrous/doUnEquip(obj/item/I, force, silent = FALSE) if(..()) update_inv_hands() if(I == internal_storage) diff --git a/code/modules/mob/living/simple_animal/guardian/types/ranged.dm b/code/modules/mob/living/simple_animal/guardian/types/ranged.dm index e7c4e2f352..e2bcdd5684 100644 --- a/code/modules/mob/living/simple_animal/guardian/types/ranged.dm +++ b/code/modules/mob/living/simple_animal/guardian/types/ranged.dm @@ -110,6 +110,7 @@ /obj/effect/snare/Crossed(AM as mob|obj) + . = ..() if(isliving(AM) && spawner && spawner.summoner && AM != spawner && !spawner.hasmatchingsummoner(AM)) to_chat(spawner.summoner, "[AM] has crossed surveillance snare, [name].") var/list/guardians = spawner.summoner.hasparasites() diff --git a/code/modules/mob/living/simple_animal/hostile/alien.dm b/code/modules/mob/living/simple_animal/hostile/alien.dm index 86467624b5..05d6eda435 100644 --- a/code/modules/mob/living/simple_animal/hostile/alien.dm +++ b/code/modules/mob/living/simple_animal/hostile/alien.dm @@ -78,6 +78,15 @@ projectiletype = /obj/item/projectile/neurotox projectilesound = 'sound/weapons/pierce.ogg' +/mob/living/simple_animal/hostile/alien/sentinel/cube + gold_core_spawnable = NO_SPAWN + health = 220 + maxHealth = 220 + melee_damage_lower = 20 + melee_damage_upper = 20 + del_on_death = TRUE + loot = list(/obj/effect/mob_spawn/alien/corpse/humanoid/sentinel) + /mob/living/simple_animal/hostile/alien/queen name = "alien queen" diff --git a/code/modules/mob/living/simple_animal/hostile/banana_spider.dm b/code/modules/mob/living/simple_animal/hostile/banana_spider.dm index 259c0bc721..a88593ed74 100644 --- a/code/modules/mob/living/simple_animal/hostile/banana_spider.dm +++ b/code/modules/mob/living/simple_animal/hostile/banana_spider.dm @@ -22,7 +22,6 @@ speak_emote = list("chitters") mouse_opacity = 2 density = TRUE - ventcrawler = VENTCRAWLER_ALWAYS verb_say = "chitters" verb_ask = "chitters inquisitively" verb_exclaim = "chitters loudly" @@ -37,6 +36,8 @@ if(A) notify_ghosts("A banana spider has been created in \the [A.name].", source = src, action=NOTIFY_ATTACK, flashwindow = FALSE, ignore_dnr_observers = TRUE) + AddElement(/datum/element/ventcrawling, given_tier = VENTCRAWLER_ALWAYS) + /mob/living/simple_animal/banana_spider/attack_ghost(mob/user) if(key) //please stop using src. without a good reason. return diff --git a/code/modules/mob/living/simple_animal/hostile/bear.dm b/code/modules/mob/living/simple_animal/hostile/bear.dm index 1be90a07f8..c7772f780d 100644 --- a/code/modules/mob/living/simple_animal/hostile/bear.dm +++ b/code/modules/mob/living/simple_animal/hostile/bear.dm @@ -109,7 +109,7 @@ to_chat(user, "You strap the armor plating to [A] and sharpen [A.p_their()] claws with the nail filer. This was a great idea.") qdel(src) -mob/living/simple_animal/hostile/bear/butter //The mighty companion to Cak. Several functions used from it. +/mob/living/simple_animal/hostile/bear/butter //The mighty companion to Cak. Several functions used from it. name = "Terrygold" icon_state = "butterbear" icon_living = "butterbear" @@ -155,7 +155,7 @@ mob/living/simple_animal/hostile/bear/butter //The mighty companion to Cak. Seve to_chat(src, "Your name is now \"new_name\"!") name = new_name -mob/living/simple_animal/hostile/bear/butter/AttackingTarget() //Makes some attacks by the butter bear slip those who dare cross its path. +/mob/living/simple_animal/hostile/bear/butter/AttackingTarget() //Makes some attacks by the butter bear slip those who dare cross its path. if(isliving(target)) var/mob/living/L = target if((L.mobility_flags & MOBILITY_STAND)) diff --git a/code/modules/mob/living/simple_animal/hostile/bosses/boss.dm b/code/modules/mob/living/simple_animal/hostile/bosses/boss.dm index b4d60af198..98700ffaf0 100644 --- a/code/modules/mob/living/simple_animal/hostile/bosses/boss.dm +++ b/code/modules/mob/living/simple_animal/hostile/bosses/boss.dm @@ -11,7 +11,7 @@ var/datum/boss_active_timed_battle/atb var/point_regen_delay = 20 var/point_regen_amount = 1 - + sentience_type = SENTIENCE_BOSS /mob/living/simple_animal/hostile/boss/Initialize() . = ..() diff --git a/code/modules/mob/living/simple_animal/hostile/bread.dm b/code/modules/mob/living/simple_animal/hostile/bread.dm index 317827028d..70c8b0c540 100644 --- a/code/modules/mob/living/simple_animal/hostile/bread.dm +++ b/code/modules/mob/living/simple_animal/hostile/bread.dm @@ -26,7 +26,6 @@ speak_emote = list("growls") mouse_opacity = 2 density = TRUE - ventcrawler = VENTCRAWLER_ALWAYS verb_say = "growls" verb_ask = "growls inquisitively" verb_exclaim = "growls loudly" @@ -38,6 +37,7 @@ var/area/A = get_area(src) if(A) notify_ghosts("A tumor bread has been created in \the [A.name].", source = src, action=NOTIFY_ATTACK, flashwindow = FALSE, ignore_dnr_observers = TRUE) + AddElement(/datum/element/ventcrawling, given_tier = VENTCRAWLER_ALWAYS) /mob/living/simple_animal/hostile/bread/attack_ghost(mob/user) if(key) //please stop using src. without a good reason. diff --git a/code/modules/mob/living/simple_animal/hostile/giant_spider.dm b/code/modules/mob/living/simple_animal/hostile/giant_spider.dm index bde5a19518..2349685a4e 100644 --- a/code/modules/mob/living/simple_animal/hostile/giant_spider.dm +++ b/code/modules/mob/living/simple_animal/hostile/giant_spider.dm @@ -44,7 +44,6 @@ var/busy = SPIDER_IDLE pass_flags = PASSTABLE move_to_delay = 6 - ventcrawler = VENTCRAWLER_ALWAYS attack_verb_continuous = "bites" attack_verb_simple = "bite" attack_sound = 'sound/weapons/bite.ogg' @@ -63,6 +62,8 @@ lay_web = new lay_web.Grant(src) + AddElement(/datum/element/ventcrawling, given_tier = VENTCRAWLER_ALWAYS) + /mob/living/simple_animal/hostile/poison/giant_spider/Destroy() QDEL_NULL(lay_web) return ..() @@ -389,7 +390,6 @@ name = "Wrap" panel = "Spider" active = FALSE - datum/action/spell_action/action = null desc = "Wrap something or someone in a cocoon. If it's a living being, you'll also consume them, allowing you to lay eggs." ranged_mousepointer = 'icons/effects/wrap_target.dmi' action_icon = 'icons/mob/actions/actions_animal.dmi' diff --git a/code/modules/mob/living/simple_animal/hostile/headcrab.dm b/code/modules/mob/living/simple_animal/hostile/headcrab.dm index 850beeb099..5e34d17bb2 100644 --- a/code/modules/mob/living/simple_animal/hostile/headcrab.dm +++ b/code/modules/mob/living/simple_animal/hostile/headcrab.dm @@ -20,11 +20,14 @@ obj_damage = 0 environment_smash = ENVIRONMENT_SMASH_NONE speak_emote = list("squeaks") - ventcrawler = VENTCRAWLER_ALWAYS var/datum/mind/origin var/egg_lain = 0 gold_core_spawnable = NO_SPAWN //are you sure about this?? // CITADEL CHANGE, Yes. +/mob/living/simple_animal/hostile/headcrab/Initialize() + . = ..() + AddElement(/datum/element/ventcrawling, given_tier = VENTCRAWLER_ALWAYS) + /mob/living/simple_animal/hostile/headcrab/proc/Infect(mob/living/carbon/victim) var/obj/item/organ/body_egg/changeling_egg/egg = new(victim) egg.Insert(victim) diff --git a/code/modules/mob/living/simple_animal/hostile/hostile.dm b/code/modules/mob/living/simple_animal/hostile/hostile.dm index 37eb9b7f67..95f8f2acc1 100644 --- a/code/modules/mob/living/simple_animal/hostile/hostile.dm +++ b/code/modules/mob/living/simple_animal/hostile/hostile.dm @@ -511,7 +511,7 @@ DestroyObjectsInDirection(direction) -mob/living/simple_animal/hostile/proc/DestroySurroundings() // for use with megafauna destroying everything around them +/mob/living/simple_animal/hostile/proc/DestroySurroundings() // for use with megafauna destroying everything around them if(environment_smash) EscapeConfinement() for(var/dir in GLOB.cardinals) diff --git a/code/modules/mob/living/simple_animal/hostile/killertomato.dm b/code/modules/mob/living/simple_animal/hostile/killertomato.dm index b86d5d87fc..d3d3dadd1e 100644 --- a/code/modules/mob/living/simple_animal/hostile/killertomato.dm +++ b/code/modules/mob/living/simple_animal/hostile/killertomato.dm @@ -22,10 +22,13 @@ attack_verb_continuous = "slams" attack_verb_simple = "slam" attack_sound = 'sound/weapons/punch1.ogg' - ventcrawler = VENTCRAWLER_ALWAYS faction = list("plants") atmos_requirements = list("min_oxy" = 5, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) minbodytemp = 150 maxbodytemp = 500 gold_core_spawnable = HOSTILE_SPAWN + +/mob/living/simple_animal/hostile/killertomato/Initialize() + . = ..() + AddElement(/datum/element/ventcrawling, given_tier = VENTCRAWLER_ALWAYS) diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner.dm index 2a5f279386..61f1fe0c9d 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner.dm @@ -44,7 +44,9 @@ Difficulty: Medium wander = FALSE del_on_death = TRUE blood_volume = BLOOD_VOLUME_NORMAL - medal_type = BOSS_MEDAL_MINER + achievement_type = /datum/award/achievement/boss/blood_miner_kill + crusher_achievement_type = /datum/award/achievement/boss/blood_miner_crusher + score_achievement_type = /datum/award/score/blood_miner_score var/obj/item/melee/transforming/cleaving_saw/miner/miner_saw var/time_until_next_transform = 0 var/dashing = FALSE diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm index 519d6402e6..d5b78b14b6 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm @@ -51,8 +51,11 @@ Difficulty: Hard crusher_loot = list(/obj/structure/closet/crate/necropolis/bubblegum/crusher) loot = list(/obj/structure/closet/crate/necropolis/bubblegum) var/charging = 0 - medal_type = BOSS_MEDAL_BUBBLEGUM - score_type = BUBBLEGUM_SCORE + + achievement_type = /datum/award/achievement/boss/bubblegum_kill + crusher_achievement_type = /datum/award/achievement/boss/bubblegum_crusher + score_achievement_type = /datum/award/score/bubblegum_score + deathmessage = "sinks into a pool of blood, fleeing the battle. You've won, for now... " death_sound = 'sound/magic/enter_blood.ogg' @@ -153,6 +156,20 @@ Difficulty: Hard charging = 0 Goto(target, move_to_delay, minimum_distance) +/** + * Attack by override for bubblegum + * + * This is used to award the frenching achievement for hitting bubblegum with a tongue + * + * Arguments: + * * obj/item/W the item hitting bubblegum + * * mob/user The user of the item + * * params, extra parameters + */ +/mob/living/simple_animal/hostile/megafauna/bubblegum/attackby(obj/item/W, mob/user, params) + . = ..() + if(istype(W, /obj/item/organ/tongue)) + user.client?.give_award(/datum/award/achievement/misc/frenching, user) /mob/living/simple_animal/hostile/megafauna/bubblegum/Bump(atom/A) if(charging) diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm index fa3a59a6b9..787c15a5b4 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm @@ -43,9 +43,10 @@ Difficulty: Very Hard move_to_delay = 10 ranged = 1 pixel_x = -32 - del_on_death = 1 - medal_type = BOSS_MEDAL_COLOSSUS - score_type = COLOSSUS_SCORE + del_on_death = TRUE + achievement_type = /datum/award/achievement/boss/colossus_kill + crusher_achievement_type = /datum/award/achievement/boss/colossus_crusher + score_achievement_type = /datum/award/score/colussus_score crusher_loot = list(/obj/structure/closet/crate/necropolis/colossus/crusher) loot = list(/obj/structure/closet/crate/necropolis/colossus) butcher_results = list(/obj/item/stack/ore/diamond = 5, /obj/item/stack/sheet/sinew = 5, /obj/item/stack/sheet/animalhide/ashdrake = 10, /obj/item/stack/sheet/bone = 30) @@ -424,7 +425,7 @@ Difficulty: Very Hard /obj/machinery/anomalous_crystal/honk //Strips and equips you as a clown. I apologize for nothing observer_desc = "This crystal strips and equips its targets as clowns." - possible_methods = list(ACTIVATE_MOB_BUMP, ACTIVATE_SPEECH) + possible_methods = list(ACTIVATE_TOUCH) //Because We love AOE transformations! activation_sound = 'sound/items/bikehorn.ogg' /obj/machinery/anomalous_crystal/honk/ActivationReaction(mob/user) @@ -620,7 +621,6 @@ Difficulty: Very Hard density = FALSE movement_type = FLYING pass_flags = PASSTABLE | PASSGRILLE | PASSMOB - ventcrawler = VENTCRAWLER_ALWAYS mob_size = MOB_SIZE_TINY gold_core_spawnable = HOSTILE_SPAWN verb_say = "warps" @@ -648,6 +648,7 @@ Difficulty: Very Hard remove_verb(src, /mob/verb/me_verb) var/datum/atom_hud/medsensor = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED] medsensor.add_hud_to(src) + AddElement(/datum/element/ventcrawling, given_tier = VENTCRAWLER_ALWAYS) /mob/living/simple_animal/hostile/lightgeist/AttackingTarget() . = ..() diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner.dm index 5e866f95f1..2bcca74f30 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner.dm @@ -31,6 +31,9 @@ Difficulty: Extremely Hard wander = FALSE del_on_death = TRUE blood_volume = BLOOD_VOLUME_NORMAL + achievement_type = /datum/award/achievement/boss/demonic_miner_kill + crusher_achievement_type = /datum/award/achievement/boss/demonic_miner_crusher + score_achievement_type = /datum/award/score/demonic_miner_score deathmessage = "falls to the ground, decaying into plasma particles." deathsound = "bodyfall" attack_action_types = list(/datum/action/innate/megafauna_attack/frost_orbs, diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm index 477483862b..9dd2441829 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm @@ -64,8 +64,9 @@ Difficulty: Medium guaranteed_butcher_results = list(/obj/item/stack/sheet/animalhide/ashdrake = 10) var/swooping = NONE var/swoop_cooldown = 0 - medal_type = BOSS_MEDAL_DRAKE - score_type = DRAKE_SCORE + achievement_type = /datum/award/achievement/boss/drake_kill + crusher_achievement_type = /datum/award/achievement/boss/drake_crusher + score_achievement_type = /datum/award/score/drake_score deathmessage = "collapses into a pile of bones, its flesh sloughing away." death_sound = 'sound/magic/demon_dies.ogg' var/datum/action/small_sprite/smallsprite = new/datum/action/small_sprite/drake() @@ -398,6 +399,14 @@ Difficulty: Medium crusher_loot = list() butcher_results = list(/obj/item/stack/ore/diamond = 5, /obj/item/stack/sheet/sinew = 5, /obj/item/stack/sheet/bone = 30) +/mob/living/simple_animal/hostile/megafauna/dragon/lesser/transformed //ash drake balanced around player control + name = "transformed ash drake" + desc = "A sentient being transformed into an ash drake" + mob_size = MOB_SIZE_HUMAN //prevents crusher vulnerability + move_force = MOVE_FORCE_NORMAL //stops them from destroying and unanchoring shit by walking into it + environment_smash = ENVIRONMENT_SMASH_STRUCTURES //no we dont want sentient megafauna be able to delete the entire station in a minute flat + damage_coeff = list(BRUTE = 0.7, BURN = 0.5, TOX = 1, CLONE = 1, STAMINA = 0, OXY = 1) //200 health but not locked to standard movespeed, needs armor befitting of a dragon + /mob/living/simple_animal/hostile/megafauna/dragon/lesser/grant_achievement(medaltype,scoretype) return @@ -413,7 +422,8 @@ Difficulty: Medium if(L in hit_list || L == source) continue hit_list += L - L.adjustFireLoss(20) + L.adjustFireLoss(5) + L.adjust_fire_stacks(6) to_chat(L, "You're hit by [source]'s fire breath!") // deals damage to mechs diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm index 006bef974d..32300dea18 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm @@ -61,8 +61,9 @@ Difficulty: Normal loot = list(/obj/item/hierophant_club) crusher_loot = list(/obj/item/hierophant_club) wander = FALSE - medal_type = BOSS_MEDAL_HIEROPHANT - score_type = HIEROPHANT_SCORE + achievement_type = /datum/award/achievement/boss/hierophant_kill + crusher_achievement_type = /datum/award/achievement/boss/hierophant_crusher + score_achievement_type = /datum/award/score/hierophant_score del_on_death = TRUE death_sound = 'sound/magic/repulse.ogg' diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/legion.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/legion.dm index 174883650d..07c1957da2 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/legion.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/legion.dm @@ -42,8 +42,9 @@ SHITCODE AHEAD. BE ADVISED. Also comment extravaganza retreat_distance = 5 minimum_distance = 5 ranged_cooldown_time = 10 - medal_type = BOSS_MEDAL_LEGION - score_type = LEGION_SCORE + achievement_type = /datum/award/achievement/boss/legion_kill + crusher_achievement_type = /datum/award/achievement/boss/legion_crusher + score_achievement_type = /datum/award/score/legion_score pixel_y = -16 pixel_x = -32 loot = list(/obj/item/stack/sheet/bone = 3) diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm index 7009f13f36..e2d6602a88 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm @@ -28,24 +28,31 @@ layer = LARGE_MOB_LAYER //Looks weird with them slipping under mineral walls and cameras and shit otherwise flags_1 = PREVENT_CONTENTS_EXPLOSION_1 | HEAR_1 has_field_of_vision = FALSE //You are a frikkin boss - /// Crusher loot dropped when fauna killed with a crusher + /// Crusher loot dropped when the megafauna is killed with a crusher var/list/crusher_loot - var/medal_type - /// Score given to players when the fauna is killed - var/score_type = BOSS_SCORE - /// If the megafauna is actually killed (vs entering another phase) + /// Achievement given to surrounding players when the megafauna is killed + var/achievement_type + /// Crusher achievement given to players when megafauna is killed + var/crusher_achievement_type + /// Score given to players when megafauna is killed + var/score_achievement_type + /// If the megafauna was actually killed (not just dying, then transforming into another type) var/elimination = 0 /// Modifies attacks when at lower health var/anger_modifier = 0 /// Internal tracking GPS inside fauna var/obj/item/gps/internal - /// Next time fauna can use a melee attack + /// Next time the megafauna can use a melee attack var/recovery_time = 0 - - var/true_spawn = TRUE // if this is a megafauna that should grant achievements, or have a gps signal + /// If this is a megafauna that is real (has achievements, gps signal) + var/true_spawn = TRUE + /// Range the megafauna can move from their nest (if they have one var/nest_range = 10 - var/chosen_attack = 1 // chosen attack num + /// The chosen attack by the megafauna + var/chosen_attack = 1 + /// Attack actions, sets chosen_attack to the number in the action var/list/attack_action_types = list() + /// If there is a small sprite icon for players controlling the megafauna to use var/small_sprite_type /mob/living/simple_animal/hostile/megafauna/Initialize(mapload) @@ -73,23 +80,22 @@ return return ..() -/mob/living/simple_animal/hostile/megafauna/death(gibbed) +/mob/living/simple_animal/hostile/megafauna/death(gibbed, list/force_grant) if(health > 0) return - else - var/datum/status_effect/crusher_damage/C = has_status_effect(STATUS_EFFECT_CRUSHERDAMAGETRACKING) - var/crusher_kill = FALSE - if(C && crusher_loot && C.total_damage >= maxHealth * 0.6) - spawn_crusher_loot() - crusher_kill = TRUE - if(!(flags_1 & ADMIN_SPAWNED_1)) - var/tab = "megafauna_kills" - if(crusher_kill) - tab = "megafauna_kills_crusher" + var/datum/status_effect/crusher_damage/crusher_dmg = has_status_effect(STATUS_EFFECT_CRUSHERDAMAGETRACKING) + var/crusher_kill = FALSE + if(crusher_dmg && crusher_loot && crusher_dmg.total_damage >= maxHealth * 0.6) + spawn_crusher_loot() + crusher_kill = TRUE + if(true_spawn && !(flags_1 & ADMIN_SPAWNED_1)) + var/tab = "megafauna_kills" + if(crusher_kill) + tab = "megafauna_kills_crusher" + if(!elimination) //used so the achievment only occurs for the last legion to die. + grant_achievement(achievement_type, score_achievement_type, crusher_kill, force_grant) SSblackbox.record_feedback("tally", tab, 1, "[initial(name)]") - if(!elimination) //used so the achievment only occurs for the last legion to die. - grant_achievement(medal_type, score_type, crusher_kill) - ..() + return ..() /mob/living/simple_animal/hostile/megafauna/proc/spawn_crusher_loot() loot = crusher_loot @@ -143,26 +149,29 @@ if(EXPLODE_LIGHT) adjustBruteLoss(50) -/mob/living/simple_animal/hostile/megafauna/proc/SetRecoveryTime(buffer_time) +/// Sets the next time the megafauna can use a melee or ranged attack, in deciseconds +/mob/living/simple_animal/hostile/megafauna/proc/SetRecoveryTime(buffer_time, ranged_buffer_time) recovery_time = world.time + buffer_time - ranged_cooldown = max(ranged_cooldown, world.time + buffer_time) // CITADEL BANDAID FIX FOR MEGAFAUNA NOT RESPECTING RECOVERY TIME. + ranged_cooldown = world.time + buffer_time + if(ranged_buffer_time) + ranged_cooldown = world.time + ranged_buffer_time -/mob/living/simple_animal/hostile/megafauna/proc/grant_achievement(medaltype, scoretype, crusher_kill) - if(!medal_type || (flags_1 & ADMIN_SPAWNED_1)) //Don't award medals if the medal type isn't set +/// Grants medals and achievements to surrounding players +/mob/living/simple_animal/hostile/megafauna/proc/grant_achievement(medaltype, scoretype, crusher_kill, list/grant_achievement = list()) + if(!achievement_type || (flags_1 & ADMIN_SPAWNED_1) || !SSachievements.achievements_enabled) //Don't award medals if the medal type isn't set return FALSE - if(!SSmedals.hub_enabled) // This allows subtypes to carry on other special rewards not tied with medals. (such as bubblegum's arena shuttle) - return TRUE - - for(var/mob/living/L in view(7,src)) + if(!grant_achievement.len) + for(var/mob/living/L in view(7,src)) + grant_achievement += L + for(var/mob/living/L in grant_achievement) if(L.stat || !L.client) continue - var/client/C = L.client - SSmedals.UnlockMedal("Boss [BOSS_KILL_MEDAL]", C) - SSmedals.UnlockMedal("[medaltype] [BOSS_KILL_MEDAL]", C) + L.client.give_award(/datum/award/achievement/boss/boss_killer, L) + L.client.give_award(achievement_type, L) if(crusher_kill && istype(L.get_active_held_item(), /obj/item/kinetic_crusher)) - SSmedals.UnlockMedal("[medaltype] [BOSS_KILL_MEDAL_CRUSHER]", C) - SSmedals.SetScore(BOSS_SCORE, C, 1) - SSmedals.SetScore(score_type, C, 1) + L.client.give_award(crusher_achievement_type, L) + L.client.give_award(/datum/award/score/boss_score, L) //Score progression for bosses killed in general + L.client.give_award(score_achievement_type, L) //Score progression for specific boss killed return TRUE /datum/action/innate/megafauna_attack diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/swarmer.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/swarmer.dm index db6468d1b5..923a626b28 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/swarmer.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/swarmer.dm @@ -48,8 +48,9 @@ GLOBAL_LIST_INIT(AISwarmerCapsByType, list(/mob/living/simple_animal/hostile/swa health = 750 maxHealth = 750 //""""low-ish"""" HP because it's a passive boss, and the swarm itself is the real foe mob_biotypes = MOB_ROBOTIC - medal_type = BOSS_MEDAL_SWARMERS - score_type = SWARMER_BEACON_SCORE + achievement_type = /datum/award/achievement/boss/swarmer_beacon_kill + crusher_achievement_type = /datum/award/achievement/boss/swarmer_beacon_crusher + score_achievement_type = /datum/award/score/swarmer_beacon_score faction = list("mining", "boss", "swarmer") weather_immunities = list("lava","ash") stop_automated_movement = TRUE diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/basilisk.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/basilisk.dm index 61be1f6287..cee7e2979d 100644 --- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/basilisk.dm +++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/basilisk.dm @@ -94,16 +94,17 @@ consume_bait() /mob/living/simple_animal/hostile/asteroid/basilisk/watcher/proc/consume_bait() - var/obj/item/stack/ore/diamond/diamonds = locate(/obj/item/stack/ore/diamond) in oview(src, 9) - var/obj/item/pen/survival/bait = locate(/obj/item/pen/survival) in oview(src, 9) - if(!diamonds && !bait) - return + var/list/L = list() + for(var/obj/O in view(src, 9)) + L += O + var/obj/item/stack/ore/diamond/diamonds = locate(/obj/item/stack/ore/diamond) in L if(diamonds) var/distanced = 0 distanced = get_dist(loc,diamonds.loc) if(distanced <= 1 && diamonds) qdel(diamonds) src.visible_message("[src] consumes [diamonds], and it disappears! ...At least, you think.") + var/obj/item/pen/survival/bait = locate(/obj/item/pen/survival) in L if(bait) var/distanceb = 0 distanceb = get_dist(loc,bait.loc) diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/goliath_broodmother.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/goliath_broodmother.dm index 4e8f4f6160..ef51dc572f 100644 --- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/goliath_broodmother.dm +++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/goliath_broodmother.dm @@ -219,6 +219,9 @@ /obj/effect/temp_visual/goliath_tentacle/broodmother/patch/Initialize(mapload, new_spawner) . = ..() + INVOKE_ASYNC(src, .proc/createpatch) + +/obj/effect/temp_visual/goliath_tentacle/broodmother/patch/proc/createpatch() var/tentacle_locs = spiral_range_turfs(1, get_turf(src)) for(var/T in tentacle_locs) new /obj/effect/temp_visual/goliath_tentacle/broodmother(T, spawner) diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/gutlunch.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/gutlunch.dm index 0ccc4525c7..e35220a920 100644 --- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/gutlunch.dm +++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/gutlunch.dm @@ -27,7 +27,6 @@ friendly_verb_continuous = "pinches" friendly_verb_simple = "pinch" a_intent = INTENT_HELP - ventcrawler = VENTCRAWLER_ALWAYS gold_core_spawnable = FRIENDLY_SPAWN stat_attack = UNCONSCIOUS gender = NEUTER @@ -50,6 +49,7 @@ /mob/living/simple_animal/hostile/asteroid/gutlunch/Initialize() udder = new() . = ..() + AddElement(/datum/element/ventcrawling, given_tier = VENTCRAWLER_ALWAYS) /mob/living/simple_animal/hostile/asteroid/gutlunch/CanAttack(atom/the_target) // Gutlunch-specific version of CanAttack to handle stupid stat_exclusive = true crap so we don't have to do it for literally every single simple_animal/hostile except the two that spawn in lavaland if(isturf(the_target) || !the_target || the_target.type == /atom/movable/lighting_object) // bail out on invalids diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/hivelord.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/hivelord.dm index 11ce4f9214..446fe80c7d 100644 --- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/hivelord.dm +++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/hivelord.dm @@ -42,6 +42,8 @@ A.GiveTarget(target) A.friends = friends A.faction = faction.Copy() + if(!A == /mob/living/simple_animal/hostile/poison/bees/toxin) + A.my_creator = type ranged_cooldown = world.time + ranged_cooldown_time /mob/living/simple_animal/hostile/asteroid/hivelord/AttackingTarget() @@ -88,6 +90,7 @@ density = FALSE del_on_death = 1 var/swarming = FALSE + var/my_creator = null /mob/living/simple_animal/hostile/asteroid/hivelordbrood/Initialize() . = ..() @@ -205,11 +208,7 @@ /mob/living/simple_animal/hostile/asteroid/hivelordbrood/legion/proc/infest(mob/living/carbon/human/H) visible_message("[name] burrows into the flesh of [H]!") - var/mob/living/simple_animal/hostile/asteroid/hivelord/legion/L - if(HAS_TRAIT(H, TRAIT_DWARF)) //dwarf legions aren't just fluff! - L = new /mob/living/simple_animal/hostile/asteroid/hivelord/legion/dwarf(H.loc) - else - L = new(H.loc) + var/mob/living/simple_animal/hostile/asteroid/hivelord/legion/L = check_infest_type(H) visible_message("[L] staggers to [L.p_their()] feet!") H.death() H.adjustBruteLoss(1000) @@ -217,6 +216,20 @@ H.forceMove(L) qdel(src) +/mob/living/simple_animal/hostile/asteroid/hivelordbrood/legion/proc/check_infest_type(mob/living/carbon/human/human) + var/mob/living/simple_animal/hostile/asteroid/hivelord/legion/L + var/list/blacklisted_types = list(/mob/living/simple_animal/hostile/asteroid/hivelord/legion/dwarf) + if(HAS_TRAIT(human, TRAIT_DWARF)) //dwarf legions aren't just fluff! + L = new /mob/living/simple_animal/hostile/asteroid/hivelord/legion/dwarf(human.loc) + else if(my_creator) + if(my_creator in blacklisted_types) + L = new(human.loc) + else + L = new my_creator(human.loc) + else + L = new(human.loc) + return L + //Advanced Legion is slightly tougher to kill and can raise corpses (revive other legions) /mob/living/simple_animal/hostile/asteroid/hivelord/legion/advanced stat_attack = DEAD @@ -398,7 +411,6 @@ uniform = /obj/item/clothing/under/color/grey belt = /obj/item/tank/internals/emergency_oxygen mask = /obj/item/clothing/mask/gas - ears = /obj/item/radio/headset gloves = /obj/item/clothing/gloves/color/fyellow id = /obj/item/card/id/silver/reaper //looks cool and has a fancy name but only a 1% chance if(prob(99)) @@ -412,7 +424,7 @@ back = /obj/item/spear else if(prob(80)) //Now they dont always have a backpack back = /obj/item/storage/backpack - backpack_contents = list(/obj/item/stack/cable_coil = 1, /obj/item/assembly/flash = 1, /obj/item/storage/fancy/donut_box = 1, /obj/item/storage/fancy/cigarettes/cigpack_shadyjims = 1, /obj/item/lighter = 1) + backpack_contents = list(/obj/item/stack/cable_coil = 1, /obj/item/storage/fancy/donut_box = 1, /obj/item/storage/fancy/cigarettes/cigpack_shadyjims = 1, /obj/item/lighter = 1) if(prob(90)) r_pocket = /obj/item/kitchen/knife if(prob(60)) @@ -423,7 +435,6 @@ suit = /obj/item/clothing/suit/hooded/bee_costume shoes = /obj/item/clothing/shoes/sneakers/yellow gloves = /obj/item/clothing/gloves/color/yellow - ears = /obj/item/radio/headset belt = /obj/item/storage/belt/fannypack/yellow id_job = "Assisant" id = /obj/item/card/id diff --git a/code/modules/mob/living/simple_animal/hostile/mushroom.dm b/code/modules/mob/living/simple_animal/hostile/mushroom.dm index 9101430ccc..f41746ea69 100644 --- a/code/modules/mob/living/simple_animal/hostile/mushroom.dm +++ b/code/modules/mob/living/simple_animal/hostile/mushroom.dm @@ -28,7 +28,6 @@ stat_attack = DEAD mouse_opacity = MOUSE_OPACITY_ICON speed = 1 - ventcrawler = VENTCRAWLER_ALWAYS robust_searching = 1 unique_name = 1 speak_emote = list("squeaks") @@ -41,6 +40,10 @@ var/static/mutable_appearance/cap_living //Where we store our cap icons so we dont generate them constantly to update our icon var/static/mutable_appearance/cap_dead +/mob/living/simple_animal/hostile/mushroom/Initialize() + . = ..() + AddElement(/datum/element/ventcrawling, given_tier = VENTCRAWLER_ALWAYS) + /mob/living/simple_animal/hostile/mushroom/examine(mob/user) . = ..() if(health >= maxHealth) diff --git a/code/modules/mob/living/simple_animal/hostile/regalrat.dm b/code/modules/mob/living/simple_animal/hostile/regalrat.dm index 77b2d4268a..77731f0ea4 100644 --- a/code/modules/mob/living/simple_animal/hostile/regalrat.dm +++ b/code/modules/mob/living/simple_animal/hostile/regalrat.dm @@ -23,7 +23,6 @@ attack_verb_continuous = "slashes" attack_verb_simple = "slash" attack_sound = 'sound/weapons/punch1.ogg' - ventcrawler = VENTCRAWLER_ALWAYS unique_name = TRUE faction = list("rat") var/datum/action/cooldown/coffer @@ -42,6 +41,8 @@ key = C.key notify_ghosts("All rise for the rat king, ascendant to the throne in \the [get_area(src)].", source = src, action = NOTIFY_ORBIT, flashwindow = FALSE) + AddElement(/datum/element/ventcrawling, given_tier = VENTCRAWLER_ALWAYS) + /mob/living/simple_animal/hostile/regalrat/handle_automated_action() if(prob(20)) riot.Trigger() @@ -175,7 +176,6 @@ health = 15 butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab = 1) density = FALSE - ventcrawler = VENTCRAWLER_ALWAYS pass_flags = PASSTABLE | PASSGRILLE | PASSMOB mob_size = MOB_SIZE_TINY mob_biotypes = MOB_ORGANIC|MOB_BEAST @@ -185,6 +185,7 @@ . = ..() SSmobs.cheeserats += src AddComponent(/datum/component/swarming) + AddElement(/datum/element/ventcrawling, given_tier = VENTCRAWLER_ALWAYS) /mob/living/simple_animal/hostile/rat/Destroy() SSmobs.cheeserats -= src diff --git a/code/modules/mob/living/simple_animal/hostile/retaliate/bat.dm b/code/modules/mob/living/simple_animal/hostile/retaliate/bat.dm index 9045ccef51..cb1ee6c6f6 100644 --- a/code/modules/mob/living/simple_animal/hostile/retaliate/bat.dm +++ b/code/modules/mob/living/simple_animal/hostile/retaliate/bat.dm @@ -30,7 +30,6 @@ attack_sound = 'sound/weapons/bite.ogg' obj_damage = 0 environment_smash = ENVIRONMENT_SMASH_NONE - ventcrawler = VENTCRAWLER_ALWAYS mob_size = MOB_SIZE_TINY movement_type = FLYING speak_emote = list("squeaks") @@ -38,11 +37,14 @@ var/min_oxy = 0 var/max_tox = 0 - //Space bats need no air to fly in. atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) minbodytemp = 0 +/mob/living/simple_animal/hostile/retaliate/bat/Initialize() + . = ..() + AddElement(/datum/element/ventcrawling, given_tier = VENTCRAWLER_ALWAYS) + /mob/living/simple_animal/hostile/retaliate/bat/secbat name = "Security Bat" icon_state = "secbat" diff --git a/code/modules/mob/living/simple_animal/hostile/retaliate/clown.dm b/code/modules/mob/living/simple_animal/hostile/retaliate/clown.dm index dec2159dc0..96418686d3 100644 --- a/code/modules/mob/living/simple_animal/hostile/retaliate/clown.dm +++ b/code/modules/mob/living/simple_animal/hostile/retaliate/clown.dm @@ -137,7 +137,6 @@ emote_see = list("honks", "sweats", "jiggles", "contemplates its existence") speak_chance = 5 dextrous = TRUE - ventcrawler = VENTCRAWLER_ALWAYS maxHealth = 140 health = 140 speed = -5 @@ -147,6 +146,10 @@ obj_damage = 5 loot = list(/obj/item/clothing/suit/hooded/bloated_human, /obj/item/clothing/mask/gas/clown_hat, /obj/effect/gibspawner/human, /obj/item/soap) +/mob/living/simple_animal/hostile/retaliate/clown/fleshclown/Initialize() + . = ..() + AddElement(/datum/element/ventcrawling, given_tier = VENTCRAWLER_ALWAYS) + /mob/living/simple_animal/hostile/retaliate/clown/longface name = "Longface" desc = "Often found walking into the bar." diff --git a/code/modules/mob/living/simple_animal/hostile/retaliate/frog.dm b/code/modules/mob/living/simple_animal/hostile/retaliate/frog.dm index 8424a0a576..2433b45cc0 100644 --- a/code/modules/mob/living/simple_animal/hostile/retaliate/frog.dm +++ b/code/modules/mob/living/simple_animal/hostile/retaliate/frog.dm @@ -22,7 +22,6 @@ response_harm_continuous = "splats" response_harm_simple = "splat" density = FALSE - ventcrawler = VENTCRAWLER_ALWAYS faction = list("hostile") attack_sound = 'sound/effects/reee.ogg' butcher_results = list(/obj/item/reagent_containers/food/snacks/nugget = 1) @@ -40,9 +39,11 @@ icon_living = "rare_frog" icon_dead = "rare_frog_dead" butcher_results = list(/obj/item/reagent_containers/food/snacks/nugget = 5) + AddElement(/datum/element/ventcrawling, given_tier = VENTCRAWLER_ALWAYS) /mob/living/simple_animal/hostile/retaliate/frog/Crossed(AM as mob|obj) + . = ..() if(!stat && isliving(AM)) var/mob/living/L = AM if(L.mob_size > MOB_SIZE_TINY) - playsound(src, stepped_sound, 50, 1) + playsound(src, stepped_sound, 50, TRUE) diff --git a/code/modules/mob/living/simple_animal/hostile/venus_human_trap.dm b/code/modules/mob/living/simple_animal/hostile/venus_human_trap.dm index fdb088934c..cc5b0b8a75 100644 --- a/code/modules/mob/living/simple_animal/hostile/venus_human_trap.dm +++ b/code/modules/mob/living/simple_animal/hostile/venus_human_trap.dm @@ -190,5 +190,5 @@ * Arguments: * * datum/beam/vine - The vine to be removed from the list. */ -mob/living/simple_animal/hostile/venus_human_trap/proc/remove_vine(datum/beam/vine, force) +/mob/living/simple_animal/hostile/venus_human_trap/proc/remove_vine(datum/beam/vine, force) vines -= vine diff --git a/code/modules/mob/living/simple_animal/parrot.dm b/code/modules/mob/living/simple_animal/parrot.dm index 6e02944885..d146bb855f 100644 --- a/code/modules/mob/living/simple_animal/parrot.dm +++ b/code/modules/mob/living/simple_animal/parrot.dm @@ -83,6 +83,8 @@ //Headset for Poly to yell at engineers :) var/obj/item/radio/headset/ears = null + /// spawns with headset + var/spawns_with_headset = FALSE //The thing the parrot is currently interested in. This gets used for items the parrot wants to pick up, mobs it wants to steal from, //mobs it wants to attack or mobs that have attacked it @@ -105,13 +107,14 @@ /mob/living/simple_animal/parrot/Initialize() . = ..() - if(!ears) - var/headset = pick(/obj/item/radio/headset/headset_sec, \ - /obj/item/radio/headset/headset_eng, \ - /obj/item/radio/headset/headset_med, \ - /obj/item/radio/headset/headset_sci, \ - /obj/item/radio/headset/headset_cargo) - ears = new headset(src) + if(spawns_with_headset) + if(!ears) + var/headset = pick(/obj/item/radio/headset/headset_sec, \ + /obj/item/radio/headset/headset_eng, \ + /obj/item/radio/headset/headset_med, \ + /obj/item/radio/headset/headset_sci, \ + /obj/item/radio/headset/headset_cargo) + ears = new headset(src) parrot_sleep_dur = parrot_sleep_max //In case someone decides to change the max without changing the duration var @@ -881,6 +884,7 @@ speak = list("Poly wanna cracker!", ":e Check the crystal, you chucklefucks!",":e Wire the solars, you lazy bums!",":e WHO TOOK THE DAMN HARDSUITS?",":e OH GOD ITS ABOUT TO DELAMINATE CALL THE SHUTTLE") gold_core_spawnable = NO_SPAWN speak_chance = 3 + spawns_with_headset = TRUE var/memory_saved = FALSE var/rounds_survived = 0 var/longest_survival = 0 @@ -1189,4 +1193,4 @@ icon_state = "mtoo-flap" icon_living = "mtoo-flap" icon_dead = "mtoo-dead" - icon_sit = "mtoo_sit" \ No newline at end of file + icon_sit = "mtoo_sit" diff --git a/code/modules/mob/living/simple_animal/slime/slime.dm b/code/modules/mob/living/simple_animal/slime/slime.dm index ab59441572..f6169be902 100644 --- a/code/modules/mob/living/simple_animal/slime/slime.dm +++ b/code/modules/mob/living/simple_animal/slime/slime.dm @@ -4,7 +4,6 @@ icon_state = "grey baby slime" pass_flags = PASSTABLE mob_size = MOB_SIZE_SMALL - ventcrawler = VENTCRAWLER_ALWAYS gender = NEUTER var/is_adult = 0 var/docile = 0 @@ -108,6 +107,8 @@ AddComponent(/datum/component/footstep, FOOTSTEP_MOB_SLIME, 7.5) set_nutrition(rand(650, 800)) + AddElement(/datum/element/ventcrawling, given_tier = VENTCRAWLER_ALWAYS) + /mob/living/simple_animal/slime/Destroy() for (var/A in actions) var/datum/action/AC = A @@ -251,7 +252,7 @@ Feedon(Food) return ..() -/mob/living/simple_animal/slime/doUnEquip(obj/item/W) +/mob/living/simple_animal/slime/doUnEquip(obj/item/W, silent = FALSE) return /mob/living/simple_animal/slime/start_pulling(atom/movable/AM, state, force = move_force, supress_message = FALSE) diff --git a/code/modules/mob/living/ventcrawling.dm b/code/modules/mob/living/ventcrawling.dm index 4c00fd0033..9ade9f097f 100644 --- a/code/modules/mob/living/ventcrawling.dm +++ b/code/modules/mob/living/ventcrawling.dm @@ -5,7 +5,7 @@ GLOBAL_LIST_INIT(ventcrawl_machinery, typecacheof(list( //VENTCRAWLING -/mob/living/proc/handle_ventcrawl(atom/A) +/mob/living/proc/handle_ventcrawl(atom/A, ventcrawler) if(!ventcrawler || !Adjacent(A)) return . = TRUE //return value to stop the client from being shown the turf contents stat tab on alt-click. @@ -59,7 +59,7 @@ GLOBAL_LIST_INIT(ventcrawl_machinery, typecacheof(list( if(!client) return - if(iscarbon(src) && ventcrawler == VENTCRAWLER_NUDE) + if(iscarbon(src) && ventcrawler==VENTCRAWLER_NUDE) if(length(get_equipped_items(include_pockets = TRUE)) || get_num_held_items()) to_chat(src, "You can't crawl around in the ventilation ducts with items!") return diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm index dc81b3cfbf..ba2399b831 100644 --- a/code/modules/mob/mob_defines.dm +++ b/code/modules/mob/mob_defines.dm @@ -177,3 +177,6 @@ var/list/ability_actions /// ability = list(data). see __DEFINES/mobs/innate_abilities.dm var/list/ability_properties + + ///Override for sound_environments. If this is set the user will always hear a specific type of reverb (Instead of the area defined reverb) + var/sound_environment_override = SOUND_ENVIRONMENT_NONE diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm index 605f0a2d55..4b0f505067 100644 --- a/code/modules/mob/mob_helpers.dm +++ b/code/modules/mob/mob_helpers.dm @@ -408,7 +408,7 @@ It's fairly easy to fix if dealing with single letters but not so much with comp return return TRUE -/atom/proc/hasSiliconAccessInArea(mob/user, flags = PRIVILEDGES_SILICON) +/atom/proc/hasSiliconAccessInArea(mob/user, flags = PRIVILEGES_SILICON) return user.silicon_privileges & (flags) || (user.siliconaccesstoggle && (get_area(src) in user.siliconaccessareas)) /mob/proc/toggleSiliconAccessArea(area/area) @@ -496,7 +496,7 @@ It's fairly easy to fix if dealing with single letters but not so much with comp colored_message = "[message]" else colored_message = "[message]" - + //This makes readability a bit better for admins. switch(message_type) if(LOG_WHISPER) @@ -507,7 +507,7 @@ It's fairly easy to fix if dealing with single letters but not so much with comp colored_message = "(ASAY) [colored_message]" if(LOG_EMOTE) colored_message = "(EMOTE) [colored_message]" - + var/list/timestamped_message = list("\[[TIME_STAMP("hh:mm:ss", FALSE)]\] [key_name(src)] [loc_name(src)] (Event #[LAZYLEN(logging[smessage_type])])" = colored_message) logging[smessage_type] += timestamped_message diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm index e977c397c9..c467605f37 100644 --- a/code/modules/mob/mob_movement.dm +++ b/code/modules/mob/mob_movement.dm @@ -73,6 +73,7 @@ return FALSE //We are now going to move var/add_delay = mob.movement_delay() + mob.set_glide_size(DELAY_TO_GLIDE_SIZE(add_delay * ( (NSCOMPONENT(direction) && EWCOMPONENT(direction)) ? 2 : 1 ) ), FALSE) // set it now in case of pulled objects if(old_move_delay + (add_delay*MOVEMENT_DELAY_BUFFER_DELTA) + MOVEMENT_DELAY_BUFFER > world.time) move_delay = old_move_delay else @@ -95,6 +96,7 @@ if((direction & (direction - 1)) && mob.loc == n) //moved diagonally successfully add_delay *= 2 + mob.set_glide_size(DELAY_TO_GLIDE_SIZE(add_delay), FALSE) move_delay += add_delay if(.) // If mob is null here, we deserve the runtime if(mob.throwing) @@ -104,6 +106,8 @@ if(AM && AM.density && !SEND_SIGNAL(L, COMSIG_COMBAT_MODE_CHECK, COMBAT_MODE_ACTIVE) && !ismob(AM)) L.setDir(turn(L.dir, 180)) + last_move = world.time + SEND_SIGNAL(mob, COMSIG_MOB_CLIENT_MOVE, src, direction, n, oldloc, add_delay) /// Process_Grab(): checks for grab, attempts to break if so. Return TRUE to prevent movement. diff --git a/code/modules/mob/say_vr.dm b/code/modules/mob/say_vr.dm index c5c53dc876..94bf45c1b0 100644 --- a/code/modules/mob/say_vr.dm +++ b/code/modules/mob/say_vr.dm @@ -3,14 +3,14 @@ ////////////////////////////////////////////////////// /mob/proc/get_top_level_mob() - if(istype(src.loc,/mob)&&src.loc!=src) - var/mob/M=src.loc + if(ismob(src.loc) && src.loc != src) + var/mob/M = src.loc return M.get_top_level_mob() return src -proc/get_top_level_mob(var/mob/S) - if(istype(S.loc,/mob)&&S.loc!=S) - var/mob/M=S.loc +/proc/get_top_level_mob(mob/S) + if(ismob(S.loc) && S.loc != S) + var/mob/M = S.loc return M.get_top_level_mob() return S diff --git a/code/modules/mob/transform_procs.dm b/code/modules/mob/transform_procs.dm index 3a7e270567..fcaa77cac4 100644 --- a/code/modules/mob/transform_procs.dm +++ b/code/modules/mob/transform_procs.dm @@ -175,12 +175,10 @@ icon = null cut_overlays() invisibility = INVISIBILITY_MAXIMUM + new /obj/effect/temp_visual/monkeyify/humanify(loc) - transformation_timer = addtimer(CALLBACK(src, .proc/finish_humanize, tr_flags), TRANSFORMATION_DURATION, TIMER_UNIQUE) - -/mob/living/carbon/proc/finish_humanize(tr_flags) - transformation_timer = null + sleep(TRANSFORMATION_DURATION) //This entire proc CANNOT be split into two var/list/stored_implants = list() var/list/int_organs = list() @@ -202,6 +200,8 @@ var/mob/living/carbon/human/O = new( loc ) for(var/obj/item/C in O.loc) + if(C.anchored) + continue O.equip_to_appropriate_slot(C) dna.transfer_identity(O) diff --git a/code/modules/modular_computers/computers/_modular_computer_shared.dm b/code/modules/modular_computers/computers/_modular_computer_shared.dm index 9dde9a0c49..77888fdf01 100644 --- a/code/modules/modular_computers/computers/_modular_computer_shared.dm +++ b/code/modules/modular_computers/computers/_modular_computer_shared.dm @@ -48,8 +48,8 @@ var/multiple_slots = istype(card_slot) && istype(card_slot2) if(card_slot) if(card_slot?.stored_card || card_slot2?.stored_card) - var/obj/item/card/id/first_ID = card_slot.stored_card - var/obj/item/card/id/second_ID = card_slot2.stored_card + var/obj/item/card/id/first_ID = card_slot?.stored_card + var/obj/item/card/id/second_ID = card_slot2?.stored_card var/multiple_cards = istype(first_ID) && istype(second_ID) if(user_is_adjacent) . += "It has [multiple_slots ? "two slots" : "a slot"] for identification cards installed[multiple_cards ? " which contain [first_ID] and [second_ID]" : ", one of which contains [first_ID ? first_ID : second_ID]"]." diff --git a/code/modules/modular_computers/computers/item/computer.dm b/code/modules/modular_computers/computers/item/computer.dm index 63cb1cc5fa..131a098258 100644 --- a/code/modules/modular_computers/computers/item/computer.dm +++ b/code/modules/modular_computers/computers/item/computer.dm @@ -9,6 +9,7 @@ var/light_on = FALSE integrity_failure = 0.5 max_integrity = 100 + rad_flags = RAD_PROTECT_CONTENTS armor = list("melee" = 0, "bullet" = 20, "laser" = 20, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 0, "acid" = 0) var/enabled = 0 // Whether the computer is turned on. @@ -20,6 +21,10 @@ var/last_battery_percent = 0 // Used for deciding if battery percentage has chandged var/last_world_time = "00:00" var/list/last_header_icons + ///Looping sound for when the computer is on + var/datum/looping_sound/computer/soundloop + ///Whether or not this modular computer uses the looping sound + var/looping_sound = TRUE var/base_active_power_usage = 50 // Power usage when the computer is open (screen is active) and can be interacted with. Remember hardware can use power too. var/base_idle_power_usage = 5 // Power usage when the computer is idle and screen is off (currently only applies to laptops) @@ -56,11 +61,14 @@ physical = src comp_light_color = "#FFFFFF" idle_threads = list() + if(looping_sound) + soundloop = new(list(src), enabled) update_icon() /obj/item/modular_computer/Destroy() kill_program(forced = TRUE) STOP_PROCESSING(SSobj, src) + QDEL_NULL(soundloop) for(var/H in all_components) var/obj/item/computer_hardware/CH = all_components[H] if(CH.holder == src) @@ -103,7 +111,6 @@ var/obj/item/computer_hardware/card_slot/card_slot = all_components[MC_CARD] var/obj/item/computer_hardware/card_slot/card_slot2 = all_components[MC_CARD2] if(!(card_slot || card_slot2)) - //to_chat(user, "There isn't anywhere you can fit a card into on this computer.") return FALSE var/obj/item/card/inserting_id = inserting_item.RemoveID() @@ -112,7 +119,6 @@ if((card_slot?.try_insert(inserting_id)) || (card_slot2?.try_insert(inserting_id))) return TRUE - //to_chat(user, "This computer doesn't have an open card slot.") return FALSE /obj/item/modular_computer/MouseDrop(obj/over_object, src_location, over_location) @@ -198,7 +204,7 @@ to_chat(user, "You send an activation signal to \the [src], but it responds with an error code. It must be damaged.") else to_chat(user, "You press the power button, but the computer fails to boot up, displaying variety of errors before shutting down again.") - return + return FALSE // If we have a recharger, enable it automatically. Lets computer without a battery work. var/obj/item/computer_hardware/recharger/recharger = all_components[MC_CHARGE] @@ -210,24 +216,28 @@ to_chat(user, "You send an activation signal to \the [src], turning it on.") else to_chat(user, "You press the power button and start up \the [src].") + if(looping_sound) + soundloop.start() enabled = 1 update_icon() ui_interact(user) + return TRUE else // Unpowered if(issynth) to_chat(user, "You send an activation signal to \the [src] but it does not respond.") else to_chat(user, "You press the power button but \the [src] does not respond.") + return FALSE // Process currently calls handle_power(), may be expanded in future if more things are added. -/obj/item/modular_computer/process() +/obj/item/modular_computer/process(delta_time) if(!enabled) // The computer is turned off last_power_usage = 0 - return 0 + return if(obj_integrity <= integrity_failure * max_integrity) shutdown_computer() - return 0 + return if(active_program && active_program.requires_ntnet && !get_ntnet_status(active_program.requires_ntnet_feature)) active_program.event_networkfailure(0) // Active program requires NTNet to run but we've just lost connection. Crash. @@ -239,7 +249,7 @@ if(active_program) if(active_program.program_state != PROGRAM_STATE_KILLED) - active_program.process_tick() + active_program.process_tick(delta_time) active_program.ntnet_status = get_ntnet_status() else active_program = null @@ -247,14 +257,36 @@ for(var/I in idle_threads) var/datum/computer_file/program/P = I if(P.program_state != PROGRAM_STATE_KILLED) - P.process_tick() + P.process_tick(delta_time) P.ntnet_status = get_ntnet_status() else idle_threads.Remove(P) - handle_power() // Handles all computer power interaction + handle_power(delta_time) // Handles all computer power interaction //check_update_ui_need() +/** + * Displays notification text alongside a soundbeep when requested to by a program. + * + * After checking tha the requesting program is allowed to send an alert, creates + * a visible message of the requested text alongside a soundbeep. This proc adds + * text to indicate that the message is coming from this device and the program + * on it, so the supplied text should be the exact message and ending punctuation. + * + * Arguments: + * The program calling this proc. + * The message that the program wishes to display. + */ + +/obj/item/modular_computer/proc/alert_call(datum/computer_file/program/caller, alerttext, sound = 'sound/machines/twobeep_high.ogg') + if(!caller || !caller.alert_able || caller.alert_silenced || !alerttext) //Yeah, we're checking alert_able. No, you don't get to make alerts that the user can't silence. + return + playsound(src, sound, 50, TRUE) + visible_message("The [src] displays a [caller.filedesc] notification: [alerttext]") + var/mob/living/holder = loc + if(istype(holder)) + to_chat(holder, "[icon2html(src)] The [src] displays a [caller.filedesc] notification: [alerttext]") + // Function used by NanoUI's to obtain data for header. All relevant entries begin with "PC_" /obj/item/modular_computer/proc/get_header_data() var/list/data = list() @@ -344,13 +376,76 @@ for(var/datum/computer_file/program/P in idle_threads) P.kill_program(forced = TRUE) idle_threads.Remove(P) + if(looping_sound) + soundloop.stop() if(loud) physical.visible_message("\The [src] shuts down.") enabled = 0 update_icon() +/** + * Toggles the computer's flashlight, if it has one. + * + * Called from ui_act(), does as the name implies. + * It is seperated from ui_act() to be overwritten as needed. +*/ +/obj/item/modular_computer/proc/toggle_flashlight() + if(!has_light) + return FALSE + light_on = !light_on + if(light_on) + set_light(comp_light_luminosity, 1, comp_light_color) + else + set_light(0) + return TRUE + +/** + * Sets the computer's light color, if it has a light. + * + * Called from ui_act(), this proc takes a color string and applies it. + * It is seperated from ui_act() to be overwritten as needed. + * Arguments: + ** color is the string that holds the color value that we should use. Proc auto-fails if this is null. +*/ +/obj/item/modular_computer/proc/set_flashlight_color(color) + if(!has_light || !color) + return FALSE + comp_light_color = color + // set_light_color(color) + update_light() + return TRUE + +/obj/item/modular_computer/screwdriver_act(mob/user, obj/item/tool) + if(!all_components.len) + to_chat(user, "This device doesn't have any components installed.") + return + var/list/component_names = list() + for(var/h in all_components) + var/obj/item/computer_hardware/H = all_components[h] + component_names.Add(H.name) + + var/choice = input(user, "Which component do you want to uninstall?", "Computer maintenance", null) as null|anything in sortList(component_names) + + if(!choice) + return + + if(!Adjacent(user)) + return + + var/obj/item/computer_hardware/H = find_hardware_by_name(choice) + + if(!H) + return + + uninstall_component(H, user) + return + /obj/item/modular_computer/attackby(obj/item/W as obj, mob/user as mob) + // Check for ID first + if(istype(W, /obj/item/card/id) && InsertID(W)) + return + // Insert items into the components for(var/h in all_components) var/obj/item/computer_hardware/H = all_components[h] @@ -386,31 +481,6 @@ to_chat(user, "You repair \the [src].") return - if(W.tool_behaviour == TOOL_SCREWDRIVER) - if(!all_components.len) - to_chat(user, "This device doesn't have any components installed.") - return - var/list/component_names = list() - for(var/h in all_components) - var/obj/item/computer_hardware/H = all_components[h] - component_names.Add(H.name) - - var/choice = input(user, "Which component do you want to uninstall?", "Computer maintenance", null) as null|anything in sortList(component_names) - - if(!choice) - return - - if(!Adjacent(user)) - return - - var/obj/item/computer_hardware/H = find_hardware_by_name(choice) - - if(!H) - return - - uninstall_component(H, user) - return - ..() // Used by processor to relay qdel() to machinery type. diff --git a/code/modules/modular_computers/computers/item/computer_power.dm b/code/modules/modular_computers/computers/item/computer_power.dm index b5188f43d9..92d4a812a2 100644 --- a/code/modules/modular_computers/computers/item/computer_power.dm +++ b/code/modules/modular_computers/computers/item/computer_power.dm @@ -5,7 +5,7 @@ var/obj/item/computer_hardware/recharger/recharger = all_components[MC_CHARGE] - if(recharger && recharger.check_functionality()) + if(recharger?.check_functionality()) if(recharger.use_power(amount)) return TRUE @@ -22,7 +22,7 @@ /obj/item/modular_computer/proc/give_power(amount) var/obj/item/computer_hardware/battery/battery_module = all_components[MC_CELL] - if(battery_module && battery_module.battery) + if(battery_module?.battery) return battery_module.battery.give(amount) return 0 @@ -41,10 +41,10 @@ shutdown_computer(0) // Handles power-related things, such as battery interaction, recharging, shutdown when it's discharged -/obj/item/modular_computer/proc/handle_power() +/obj/item/modular_computer/proc/handle_power(delta_time) var/obj/item/computer_hardware/recharger/recharger = all_components[MC_CHARGE] if(recharger) - recharger.process() + recharger.process(delta_time) var/power_usage = screen_on ? base_active_power_usage : base_idle_power_usage diff --git a/code/modules/modular_computers/computers/item/computer_ui.dm b/code/modules/modular_computers/computers/item/computer_ui.dm index 4a985b93c1..a9f353bca4 100644 --- a/code/modules/modular_computers/computers/item/computer_ui.dm +++ b/code/modules/modular_computers/computers/item/computer_ui.dm @@ -7,17 +7,17 @@ if(!enabled) if(ui) ui.close() - return 0 + return if(!use_power()) if(ui) ui.close() - return 0 + return // Robots don't really need to see the screen, their wireless connection works as long as computer is on. if(!screen_on && !issilicon(user)) if(ui) ui.close() - return 0 + return // If we have an active program switch to it now. if(active_program) @@ -37,8 +37,8 @@ if (!ui) ui = new(user, src, "NtosMain") ui.set_autoupdate(TRUE) - ui.open() - ui.send_asset(get_asset_datum(/datum/asset/simple/headers)) + if(ui.open()) + ui.send_asset(get_asset_datum(/datum/asset/simple/headers)) /obj/item/modular_computer/ui_data(mob/user) @@ -47,7 +47,9 @@ data["login"] = list() var/obj/item/computer_hardware/card_slot/cardholder = all_components[MC_CARD] + data["cardholder"] = FALSE if(cardholder) + data["cardholder"] = TRUE var/obj/item/card/id/stored_card = cardholder.GetID() if(stored_card) var/stored_name = stored_card.registered_name @@ -74,11 +76,11 @@ data["programs"] = list() var/obj/item/computer_hardware/hard_drive/hard_drive = all_components[MC_HDD] for(var/datum/computer_file/program/P in hard_drive.stored_files) - var/running = 0 + var/running = FALSE if(P in idle_threads) - running = 1 + running = TRUE - data["programs"] += list(list("name" = P.filename, "desc" = P.filedesc, "running" = running)) + data["programs"] += list(list("name" = P.filename, "desc" = P.filedesc, "running" = running, "icon" = P.program_icon, "alert" = P.alert_pending)) data["has_light"] = has_light data["light_on"] = light_on @@ -88,8 +90,10 @@ // Handles user's GUI input /obj/item/modular_computer/ui_act(action, params) - if(..()) + . = ..() + if(.) return + var/obj/item/computer_hardware/hard_drive/hard_drive = all_components[MC_HDD] switch(action) if("PC_exit") @@ -144,6 +148,7 @@ if(P in idle_threads) P.program_state = PROGRAM_STATE_ACTIVE active_program = P + P.alert_pending = FALSE idle_threads.Remove(P) update_icon() return @@ -159,16 +164,12 @@ return if(P.run_program(user)) active_program = P + P.alert_pending = FALSE update_icon() return 1 if("PC_toggle_light") - light_on = !light_on - if(light_on) - set_light(comp_light_luminosity, 1, comp_light_color) - else - set_light(0) - return TRUE + return toggle_flashlight() if("PC_light_color") var/mob/user = usr @@ -180,10 +181,7 @@ if(color_hex2num(new_color) < 200) //Colors too dark are rejected to_chat(user, "That color is too dark! Choose a lighter one.") new_color = null - comp_light_color = new_color - light_color = new_color - update_light() - return TRUE + return set_flashlight_color(new_color) if("PC_Eject_Disk") var/param = params["name"] diff --git a/code/modules/modular_computers/computers/item/laptop.dm b/code/modules/modular_computers/computers/item/laptop.dm index ef83140a8f..7616e31aa8 100644 --- a/code/modules/modular_computers/computers/item/laptop.dm +++ b/code/modules/modular_computers/computers/item/laptop.dm @@ -17,7 +17,7 @@ // No running around with open laptops in hands. item_flags = SLOWS_WHILE_IN_HAND - screen_on = 0 // Starts closed + screen_on = FALSE // Starts closed var/start_open = TRUE // unless this var is set to 1 var/icon_state_closed = "laptop-closed" var/w_class_open = WEIGHT_CLASS_BULKY @@ -64,17 +64,18 @@ . = ..() if(over_object == usr || over_object == src) try_toggle_open(usr) - else if(istype(over_object, /obj/screen/inventory/hand)) + return + if(istype(over_object, /obj/screen/inventory/hand)) var/obj/screen/inventory/hand/H = over_object var/mob/M = usr - if(!M.restrained() && !M.stat) - if(!isturf(loc) || !Adjacent(M)) - return - M.put_in_hand(src, H.held_index) + if(M.stat != CONSCIOUS || M.restrained()) + return + if(!isturf(loc) || !Adjacent(M)) + return + M.put_in_hand(src, H.held_index) -/obj/item/modular_computer/laptop/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags) - . = ..() +/obj/item/modular_computer/laptop/on_attack_hand(mob/user) if(screen_on && isturf(loc)) return attack_self(user) diff --git a/code/modules/modular_computers/computers/item/processor.dm b/code/modules/modular_computers/computers/item/processor.dm index 0d7b567877..970dc8bd1d 100644 --- a/code/modules/modular_computers/computers/item/processor.dm +++ b/code/modules/modular_computers/computers/item/processor.dm @@ -43,13 +43,6 @@ /obj/item/modular_computer/processor/relay_qdel() qdel(machinery_computer) -// This thing is not meant to be used on it's own, get topic data from our machinery owner. -//obj/item/modular_computer/processor/canUseTopic(atom/movable/M, be_close=FALSE, no_dexterity=FALSE, no_tk=FALSE) -// if(!machinery_computer) -// return 0 - -// return machinery_computer.canUseTopic(user, state) - /obj/item/modular_computer/processor/shutdown_computer() if(!machinery_computer) return @@ -59,3 +52,9 @@ /obj/item/modular_computer/processor/attack_ghost(mob/user) ui_interact(user) + +/obj/item/modular_computer/processor/alert_call(datum/computer_file/program/caller, alerttext) + if(!caller || !caller.alert_able || caller.alert_silenced || !alerttext) + return + playsound(src, 'sound/machines/twobeep_high.ogg', 50, TRUE) + machinery_computer.visible_message("The [src] displays a [caller.filedesc] notification: [alerttext]") diff --git a/code/modules/modular_computers/computers/item/tablet.dm b/code/modules/modular_computers/computers/item/tablet.dm index 67e8118c7a..72e1283553 100644 --- a/code/modules/modular_computers/computers/item/tablet.dm +++ b/code/modules/modular_computers/computers/item/tablet.dm @@ -14,6 +14,7 @@ slot_flags = ITEM_SLOT_ID | ITEM_SLOT_BELT has_light = TRUE //LED flashlight! comp_light_luminosity = 2.3 //Same as the PDA + looping_sound = FALSE var/has_variants = TRUE var/finish_color = null @@ -41,6 +42,7 @@ comp_light_luminosity = 6.3 has_variants = FALSE device_theme = "syndicate" + light_color = COLOR_RED /obj/item/modular_computer/tablet/nukeops/emag_act(mob/user) if(!enabled) @@ -48,3 +50,98 @@ return FALSE to_chat(user, "You swipe \the [src]. It's screen briefly shows a message reading \"MEMORY CODE INJECTION DETECTED AND SUCCESSFULLY QUARANTINED\".") return FALSE + +/// Borg Built-in tablet interface +/obj/item/modular_computer/tablet/integrated + name = "modular interface" + icon_state = "tablet-silicon" + has_light = FALSE //tablet light button actually enables/disables the borg lamp + comp_light_luminosity = 0 + has_variants = FALSE + ///Ref to the borg we're installed in. Set by the borg during our creation. + var/mob/living/silicon/robot/borgo + ///Ref to the RoboTact app. Important enough to borgs to deserve a ref. + var/datum/computer_file/program/robotact/robotact + ///IC log that borgs can view in their personal management app + var/list/borglog = list() + +/obj/item/modular_computer/tablet/integrated/Initialize(mapload) + . = ..() + vis_flags |= VIS_INHERIT_ID + borgo = loc + if(!istype(borgo)) + borgo = null + stack_trace("[type] initialized outside of a borg, deleting.") + return INITIALIZE_HINT_QDEL + +/obj/item/modular_computer/tablet/integrated/Destroy() + borgo = null + return ..() + +/obj/item/modular_computer/tablet/integrated/turn_on(mob/user) + if(borgo?.stat != DEAD) + return ..() + return FALSE + +/** + * Returns a ref to the RoboTact app, creating the app if need be. + * + * The RoboTact app is important for borgs, and so should always be available. + * This proc will look for it in the tablet's robotact var, then check the + * hard drive if the robotact var is unset, and finally attempt to create a new + * copy if the hard drive does not contain the app. If the hard drive rejects + * the new copy (such as due to lack of space), the proc will crash with an error. + * RoboTact is supposed to be undeletable, so these will create runtime messages. + */ +/obj/item/modular_computer/tablet/integrated/proc/get_robotact() + if(!borgo) + return null + if(!robotact) + var/obj/item/computer_hardware/hard_drive/hard_drive = all_components[MC_HDD] + robotact = hard_drive.find_file_by_name("robotact") + if(!robotact) + stack_trace("Cyborg [borgo] ( [borgo.type] ) was somehow missing their self-manage app in their tablet. A new copy has been created.") + robotact = new(hard_drive) + if(!hard_drive.store_file(robotact)) + qdel(robotact) + robotact = null + CRASH("Cyborg [borgo]'s tablet hard drive rejected recieving a new copy of the self-manage app. To fix, check the hard drive's space remaining. Please make a bug report about this.") + return robotact + +//Makes the light settings reflect the borg's headlamp settings +/obj/item/modular_computer/tablet/integrated/ui_data(mob/user) + . = ..() + .["has_light"] = TRUE + .["light_on"] = borgo?.lamp_intensity + .["comp_light_color"] = borgo?.lamp_color + +//Makes the flashlight button affect the borg rather than the tablet +/obj/item/modular_computer/tablet/integrated/toggle_flashlight() + if(!borgo || QDELETED(borgo)) + return FALSE + borgo.toggle_headlamp() + return TRUE + +//Makes the flashlight color setting affect the borg rather than the tablet +/obj/item/modular_computer/tablet/integrated/set_flashlight_color(color) + if(!borgo || QDELETED(borgo) || !color) + return FALSE + borgo.lamp_color = color + borgo.toggle_headlamp(FALSE, TRUE) + return TRUE + +/obj/item/modular_computer/tablet/integrated/alert_call(datum/computer_file/program/caller, alerttext, sound = 'sound/machines/twobeep_high.ogg') + if(!caller || !caller.alert_able || caller.alert_silenced || !alerttext) //Yeah, we're checking alert_able. No, you don't get to make alerts that the user can't silence. + return + borgo.playsound_local(src, sound, 50, TRUE) + to_chat(borgo, "The [src] displays a [caller.filedesc] notification: [alerttext]") + + +/obj/item/modular_computer/tablet/integrated/syndicate + icon_state = "tablet-silicon-syndicate" + device_theme = "syndicate" + + +/obj/item/modular_computer/tablet/integrated/syndicate/Initialize() + . = ..() + borgo.lamp_color = COLOR_RED //Syndicate likes it red diff --git a/code/modules/modular_computers/computers/item/tablet_presets.dm b/code/modules/modular_computers/computers/item/tablet_presets.dm index 8ac669d2bf..90dd149825 100644 --- a/code/modules/modular_computers/computers/item/tablet_presets.dm +++ b/code/modules/modular_computers/computers/item/tablet_presets.dm @@ -29,8 +29,7 @@ install_component(new /obj/item/computer_hardware/card_slot) install_component(new /obj/item/computer_hardware/network_card) install_component(new /obj/item/computer_hardware/printer/mini) - hard_drive.store_file(new /datum/computer_file/program/bounty) - //hard_drive.store_file(new /datum/computer_file/program/shipping) + // hard_drive.store_file(new /datum/computer_file/program/shipping) /obj/item/modular_computer/tablet/preset/advanced/atmos/Initialize() //This will be defunct and will be replaced when NtOS PDAs are done . = ..() @@ -38,8 +37,10 @@ /obj/item/modular_computer/tablet/preset/advanced/command/Initialize() . = ..() + var/obj/item/computer_hardware/hard_drive/small/hard_drive = find_hardware_by_name("solid state drive") install_component(new /obj/item/computer_hardware/sensorpackage) install_component(new /obj/item/computer_hardware/card_slot/secondary) + hard_drive.store_file(new /datum/computer_file/program/budgetorders) /// Given by the syndicate as part of the contract uplink bundle - loads in the Contractor Uplink. /obj/item/modular_computer/tablet/syndicate_contract_uplink/preset/uplink/Initialize() @@ -67,3 +68,11 @@ install_component(new /obj/item/computer_hardware/battery(src, /obj/item/stock_parts/cell/computer)) install_component(new /obj/item/computer_hardware/hard_drive/small/nukeops) install_component(new /obj/item/computer_hardware/network_card) + +//Borg Built-in tablet +/obj/item/modular_computer/tablet/integrated/Initialize() + . = ..() + install_component(new /obj/item/computer_hardware/processor_unit/small) + install_component(new /obj/item/computer_hardware/hard_drive/small/integrated) + install_component(new /obj/item/computer_hardware/recharger/cyborg) + install_component(new /obj/item/computer_hardware/network_card/integrated) diff --git a/code/modules/modular_computers/computers/machinery/modular_computer.dm b/code/modules/modular_computers/computers/machinery/modular_computer.dm index 0e6f4d161a..090bf1c7fc 100644 --- a/code/modules/modular_computers/computers/machinery/modular_computer.dm +++ b/code/modules/modular_computers/computers/machinery/modular_computer.dm @@ -48,7 +48,7 @@ cpu.attack_ghost(user) /obj/machinery/modular_computer/emag_act(mob/user) - . = ..() + . = ..() if(!cpu) to_chat(user, "You'd need to turn the [src] on first.") return FALSE @@ -59,7 +59,7 @@ icon_state = icon_state_powered if(!cpu || !cpu.enabled) - if (!(stat & NOPOWER) && (cpu && cpu.use_power())) + if (!(stat & NOPOWER) && (cpu?.use_power())) add_overlay(screen_icon_screensaver) else icon_state = icon_state_unpowered @@ -88,16 +88,16 @@ return ..() // Process currently calls handle_power(), may be expanded in future if more things are added. -/obj/machinery/modular_computer/process() +/obj/machinery/modular_computer/process(delta_time) if(cpu) // Keep names in sync. cpu.name = name - cpu.process() + cpu.process(delta_time) // Used in following function to reduce copypaste /obj/machinery/modular_computer/proc/power_failure(malfunction = 0) var/obj/item/computer_hardware/battery/battery_module = cpu.all_components[MC_CELL] - if(cpu && cpu.enabled) // Shut down the computer + if(cpu?.enabled) // Shut down the computer visible_message("\The [src]'s screen flickers [battery_module ? "\"BATTERY [malfunction ? "MALFUNCTION" : "CRITICAL"]\"" : "\"EXTERNAL POWER LOSS\""] warning as it shuts down unexpectedly.") if(cpu) cpu.shutdown_computer(0) @@ -106,14 +106,18 @@ // Modular computers can have battery in them, we handle power in previous proc, so prevent this from messing it up for us. /obj/machinery/modular_computer/power_change() - if(cpu && cpu.use_power()) // If MC_CPU still has a power source, PC wouldn't go offline. + if(cpu?.use_power()) // If MC_CPU still has a power source, PC wouldn't go offline. stat &= ~NOPOWER update_icon() return . = ..() +/obj/machinery/modular_computer/screwdriver_act(mob/user, obj/item/tool) + if(cpu) + return cpu.screwdriver_act(user, tool) + /obj/machinery/modular_computer/attackby(obj/item/W as obj, mob/user) - if(cpu && !(flags_1 & NODECONSTRUCT_1)) + if (user.a_intent == INTENT_HELP && cpu && !(flags_1 & NODECONSTRUCT_1)) return cpu.attackby(W, user) return ..() @@ -125,11 +129,11 @@ cpu.ex_act(severity) // switch(severity) // if(EXPLODE_DEVASTATE) - // SSexplosions.highobj += cpu + // SSexplosions.high_mov_atom += cpu // if(EXPLODE_HEAVY) - // SSexplosions.medobj += cpu + // SSexplosions.med_mov_atom += cpu // if(EXPLODE_LIGHT) - // SSexplosions.lowobj += cpu + // SSexplosions.low_mov_atom += cpu ..() // EMPs are similar to explosions, but don't cause physical damage to the casing. Instead they screw up the components diff --git a/code/modules/modular_computers/computers/machinery/modular_console.dm b/code/modules/modular_computers/computers/machinery/modular_console.dm index 5d596f98e4..0e27d81305 100644 --- a/code/modules/modular_computers/computers/machinery/modular_console.dm +++ b/code/modules/modular_computers/computers/machinery/modular_console.dm @@ -37,7 +37,7 @@ var/obj/item/computer_hardware/network_card/wired/network_card = new() cpu.install_component(network_card) - cpu.install_component(new /obj/item/computer_hardware/recharger/APC) + cpu.install_component(new /obj/item/computer_hardware/recharger/apc_recharger) cpu.install_component(new /obj/item/computer_hardware/hard_drive/super) // Consoles generally have better HDDs due to lower space limitations var/area/A = get_area(src) diff --git a/code/modules/modular_computers/file_system/program.dm b/code/modules/modular_computers/file_system/program.dm index 6d6a48d567..a86405f882 100644 --- a/code/modules/modular_computers/file_system/program.dm +++ b/code/modules/modular_computers/file_system/program.dm @@ -33,6 +33,14 @@ var/tgui_id /// Example: "something.gif" - a header image that will be rendered in computer's UI when this program is running at background. Images are taken from /icons/program_icons. Be careful not to use too large images! var/ui_header = null + /// Font Awesome icon to use as this program's icon in the modular computer main menu. Defaults to a basic program maximize window icon if not overridden. + var/program_icon = "window-maximize-o" + /// Whether this program can send alerts while minimized or closed. Used to show a mute button per program in the file manager + var/alert_able = FALSE + /// Whether the user has muted this program's ability to send alerts. + var/alert_silenced = FALSE + /// Whether to highlight our program in the main screen. Intended for alerts, but loosely available for any need to notify of changed conditions. Think Windows task bar highlighting. Available even if alerts are muted. + var/alert_pending = FALSE /datum/computer_file/program/New(obj/item/modular_computer/comp = null) ..() @@ -68,8 +76,8 @@ if(!(hardware_flag & usage_flags)) if(loud && computer && user) to_chat(user, "\The [computer] flashes a \"Hardware Error - Incompatible software\" warning.") - return 0 - return 1 + return FALSE + return TRUE /datum/computer_file/program/proc/get_signal(specific_action = 0) if(computer) @@ -77,21 +85,21 @@ return 0 // Called by Process() on device that runs us, once every tick. -/datum/computer_file/program/proc/process_tick() - return 1 +/datum/computer_file/program/proc/process_tick(delta_time) + return TRUE /** - *Check if the user can run program. Only humans can operate computer. Automatically called in run_program() - *ID must be inserted into a card slot to be read. If the program is not currently installed (as is the case when - *NT Software Hub is checking available software), a list can be given to be used instead. - *Arguments: - *user is a ref of the mob using the device. - *loud is a bool deciding if this proc should use to_chats - *access_to_check is an access level that will be checked against the ID - *transfer, if TRUE and access_to_check is null, will tell this proc to use the program's transfer_access in place of access_to_check - *access can contain a list of access numbers to check against. If access is not empty, it will be used istead of checking any inserted ID. + *Check if the user can run program. Only humans can operate computer. Automatically called in run_program() + *ID must be inserted into a card slot to be read. If the program is not currently installed (as is the case when + *NT Software Hub is checking available software), a list can be given to be used instead. + *Arguments: + *user is a ref of the mob using the device. + *loud is a bool deciding if this proc should use to_chats + *access_to_check is an access level that will be checked against the ID + *transfer, if TRUE and access_to_check is null, will tell this proc to use the program's transfer_access in place of access_to_check + *access can contain a list of access numbers to check against. If access is not empty, it will be used istead of checking any inserted ID. */ -/datum/computer_file/program/proc/can_run(mob/user, loud = FALSE, access_to_check, transfer = FALSE, var/list/access) +/datum/computer_file/program/proc/can_run(mob/user, loud = FALSE, access_to_check, transfer = FALSE, list/access) // Defaults to required_access if(!access_to_check) if(transfer && transfer_access) @@ -147,19 +155,19 @@ ID = card_holder.GetID() generate_network_log("Connection opened -- Program ID: [filename] User:[ID?"[ID.registered_name]":"None"]") program_state = PROGRAM_STATE_ACTIVE - return 1 - return 0 + return TRUE + return FALSE /** - * - *Called by the device when it is emagged. - * - *Emagging the device allows certain programs to unlock new functions. However, the program will - *need to be downloaded first, and then handle the unlock on their own in their run_emag() proc. - *The device will allow an emag to be run multiple times, so the user can re-emag to run the - *override again, should they download something new. The run_emag() proc should return TRUE if - *the emagging affected anything, and FALSE if no change was made (already emagged, or has no - *emag functions). + * + *Called by the device when it is emagged. + * + *Emagging the device allows certain programs to unlock new functions. However, the program will + *need to be downloaded first, and then handle the unlock on their own in their run_emag() proc. + *The device will allow an emag to be run multiple times, so the user can re-emag to run the + *override again, should they download something new. The run_emag() proc should return TRUE if + *the emagging affected anything, and FALSE if no change was made (already emagged, or has no + *emag functions). **/ /datum/computer_file/program/proc/run_emag() return FALSE @@ -179,8 +187,8 @@ ui = SStgui.try_update_ui(user, src, ui) if(!ui && tgui_id) ui = new(user, src, tgui_id, filedesc) - ui.open() - ui.send_asset(get_asset_datum(/datum/asset/simple/headers)) + if(ui.open()) + ui.send_asset(get_asset_datum(/datum/asset/simple/headers)) // CONVENTIONS, READ THIS WHEN CREATING NEW PROGRAM AND OVERRIDING THIS PROC: // Topic calls are automagically forwarded from NanoModule this program contains. @@ -188,18 +196,20 @@ // Calls beginning with "PC_" are reserved for computer handling (by whatever runs the program) // ALWAYS INCLUDE PARENT CALL ..() OR DIE IN FIRE. /datum/computer_file/program/ui_act(action,list/params,datum/tgui/ui) - if(..()) - return 1 + . = ..() + if(.) + return + if(computer) switch(action) if("PC_exit") computer.kill_program() ui.close() - return 1 + return TRUE if("PC_shutdown") computer.shutdown_computer() ui.close() - return 1 + return TRUE if("PC_minimize") var/mob/user = usr if(!computer.active_program || !computer.all_components[MC_CPU]) diff --git a/code/modules/modular_computers/file_system/programs/airestorer.dm b/code/modules/modular_computers/file_system/programs/airestorer.dm index 7ae6dd203a..faf2831ca1 100644 --- a/code/modules/modular_computers/file_system/programs/airestorer.dm +++ b/code/modules/modular_computers/file_system/programs/airestorer.dm @@ -9,6 +9,7 @@ transfer_access = ACCESS_HEADS available_on_ntnet = TRUE tgui_id = "NtosAiRestorer" + program_icon = "laptop-code" /// Variable dictating if we are in the process of restoring the AI in the inserted intellicard var/restoring = FALSE @@ -19,7 +20,7 @@ if(computer) ai_slot = computer.all_components[MC_AI] - if(computer && ai_slot && ai_slot.check_functionality()) + if(computer && ai_slot?.check_functionality()) if(cardcheck == 1) return ai_slot if(ai_slot.enabled && ai_slot.stored_card) @@ -31,7 +32,8 @@ return /datum/computer_file/program/aidiag/ui_act(action, params) - if(..()) + . = ..() + if(.) return var/mob/living/silicon/ai/A = get_ai() @@ -47,7 +49,7 @@ if("PRG_eject") if(computer.all_components[MC_AI]) var/obj/item/computer_hardware/ai_slot/ai_slot = computer.all_components[MC_AI] - if(ai_slot && ai_slot.stored_card) + if(ai_slot?.stored_card) ai_slot.try_eject(usr) return TRUE @@ -72,10 +74,10 @@ restoring = FALSE return ai_slot.locked = TRUE - A.adjustOxyLoss(-5, 0)//, FALSE) - A.adjustFireLoss(-5, 0)//, FALSE) - A.adjustToxLoss(-5, 0) - A.adjustBruteLoss(-5, 0) + A.adjustOxyLoss(-5, FALSE) + A.adjustFireLoss(-5, FALSE) + A.adjustToxLoss(-5, FALSE) + A.adjustBruteLoss(-5, FALSE) // Please don't forget to update health, otherwise the below if statements will probably always fail. A.updatehealth() diff --git a/code/modules/modular_computers/file_system/programs/alarm.dm b/code/modules/modular_computers/file_system/programs/alarm.dm index 55dea600e3..646d9892ba 100644 --- a/code/modules/modular_computers/file_system/programs/alarm.dm +++ b/code/modules/modular_computers/file_system/programs/alarm.dm @@ -7,6 +7,7 @@ requires_ntnet = 1 size = 5 tgui_id = "NtosStationAlertConsole" + program_icon = "bell" var/has_alert = 0 var/alarms = list("Fire" = list(), "Atmosphere" = list(), "Power" = list()) diff --git a/code/modules/modular_computers/file_system/programs/antagonist/contract_uplink.dm b/code/modules/modular_computers/file_system/programs/antagonist/contract_uplink.dm index aa361d4544..8709526de6 100644 --- a/code/modules/modular_computers/file_system/programs/antagonist/contract_uplink.dm +++ b/code/modules/modular_computers/file_system/programs/antagonist/contract_uplink.dm @@ -9,6 +9,7 @@ unsendable = 1 undeletable = 1 tgui_id = "SyndContractor" + program_icon = "tasks" var/error = "" var/info_screen = TRUE var/assigned = FALSE @@ -18,8 +19,9 @@ . = ..(user) /datum/computer_file/program/contract_uplink/ui_act(action, params) - if(..()) - return TRUE + . = ..() + if(.) + return var/mob/living/user = usr var/obj/item/computer_hardware/hard_drive/small/syndicate/hard_drive = computer.all_components[MC_HDD] diff --git a/code/modules/modular_computers/file_system/programs/antagonist/dos.dm b/code/modules/modular_computers/file_system/programs/antagonist/dos.dm index 803dadc0a0..bb3c62cac2 100644 --- a/code/modules/modular_computers/file_system/programs/antagonist/dos.dm +++ b/code/modules/modular_computers/file_system/programs/antagonist/dos.dm @@ -8,6 +8,7 @@ available_on_ntnet = FALSE available_on_syndinet = TRUE tgui_id = "NtosNetDos" + program_icon = "satellite-dish" var/obj/machinery/ntnet_relay/target = null var/dos_speed = 0 @@ -39,7 +40,8 @@ ..() /datum/computer_file/program/ntnet_dos/ui_act(action, params) - if(..()) + . = ..() + if(.) return switch(action) if("PRG_target_relay") diff --git a/code/modules/modular_computers/file_system/programs/antagonist/revelation.dm b/code/modules/modular_computers/file_system/programs/antagonist/revelation.dm index 4f1c488b9e..ba24a5ab3e 100644 --- a/code/modules/modular_computers/file_system/programs/antagonist/revelation.dm +++ b/code/modules/modular_computers/file_system/programs/antagonist/revelation.dm @@ -8,6 +8,7 @@ available_on_ntnet = FALSE available_on_syndinet = TRUE tgui_id = "NtosRevelation" + program_icon = "magnet" var/armed = 0 /datum/computer_file/program/revelation/run_program(mob/living/user) @@ -17,6 +18,12 @@ /datum/computer_file/program/revelation/proc/activate() if(computer) + if(istype(computer, /obj/item/modular_computer/tablet/integrated)) //If this is a borg's integrated tablet + var/obj/item/modular_computer/tablet/integrated/modularInterface = computer + to_chat(modularInterface.borgo,"SYSTEM PURGE DETECTED/") + addtimer(CALLBACK(modularInterface.borgo, /mob/living/silicon/robot/.proc/death), 2 SECONDS, TIMER_UNIQUE) + return + computer.visible_message("\The [computer]'s screen brightly flashes and loud electrical buzzing is heard.") computer.enabled = FALSE computer.update_icon() @@ -39,7 +46,8 @@ /datum/computer_file/program/revelation/ui_act(action, params) - if(..()) + . = ..() + if(.) return switch(action) if("PRG_arm") diff --git a/code/modules/modular_computers/file_system/programs/arcade.dm b/code/modules/modular_computers/file_system/programs/arcade.dm index 4c7b51a7fd..c330cdcbe8 100644 --- a/code/modules/modular_computers/file_system/programs/arcade.dm +++ b/code/modules/modular_computers/file_system/programs/arcade.dm @@ -6,6 +6,7 @@ requires_ntnet = FALSE size = 6 tgui_id = "NtosArcade" + program_icon = "gamepad" ///Returns TRUE if the game is being played. var/game_active = TRUE @@ -27,7 +28,7 @@ // user?.mind?.adjust_experience(/datum/skill/gaming, 1) if(boss_hp <= 0) heads_up = "You have crushed [boss_name]! Rejoice!" - playsound(computer.loc, 'sound/arcade/win.ogg', 50, TRUE, extrarange = -3, falloff = 10) + playsound(computer.loc, 'sound/arcade/win.ogg', 50) game_active = FALSE program_icon_state = "arcade_off" if(istype(computer)) @@ -37,7 +38,7 @@ sleep(10) else if(player_hp <= 0 || player_mp <= 0) heads_up = "You have been defeated... how will the station survive?" - playsound(computer.loc, 'sound/arcade/lose.ogg', 50, TRUE, extrarange = -3, falloff = 10) + playsound(computer.loc, 'sound/arcade/lose.ogg', 50) game_active = FALSE program_icon_state = "arcade_off" if(istype(computer)) @@ -57,17 +58,17 @@ return if (boss_mp <= 5) heads_up = "[boss_mpamt] magic power has been stolen from you!" - playsound(computer.loc, 'sound/arcade/steal.ogg', 50, TRUE, extrarange = -3, falloff = 10) + playsound(computer.loc, 'sound/arcade/steal.ogg', 50, TRUE) player_mp -= boss_mpamt boss_mp += boss_mpamt else if(boss_mp > 5 && boss_hp <12) heads_up = "[boss_name] heals for [bossheal] health!" - playsound(computer.loc, 'sound/arcade/heal.ogg', 50, TRUE, extrarange = -3, falloff = 10) + playsound(computer.loc, 'sound/arcade/heal.ogg', 50, TRUE) boss_hp += bossheal boss_mp -= boss_mpamt else heads_up = "[boss_name] attacks you for [boss_attackamt] damage!" - playsound(computer.loc, 'sound/arcade/hit.ogg', 50, TRUE, extrarange = -3, falloff = 10) + playsound(computer.loc, 'sound/arcade/hit.ogg', 50, TRUE) player_hp -= boss_attackamt pause_state = FALSE @@ -91,22 +92,27 @@ return data /datum/computer_file/program/arcade/ui_act(action, list/params) - if(..()) - return TRUE + . = ..() + if(.) + return + var/obj/item/computer_hardware/printer/printer if(computer) printer = computer.all_components[MC_PRINT] - // var/gamerSkillLevel = usr.mind?.get_skill_level(/datum/skill/gaming) - // var/gamerSkill = usr.mind?.get_skill_modifier(/datum/skill/gaming, SKILL_RANDS_MODIFIER) + // var/gamerSkillLevel = 0 + var/gamerSkill = 0 + // if(usr?.mind) + // gamerSkillLevel = usr.mind.get_skill_level(/datum/skill/gaming) + // gamerSkill = usr.mind.get_skill_modifier(/datum/skill/gaming, SKILL_RANDS_MODIFIER) switch(action) if("Attack") var/attackamt = 0 //Spam prevention. if(pause_state == FALSE) - attackamt = rand(2,6)// + rand(0, gamerSkill) + attackamt = rand(2,6) + rand(0, gamerSkill) pause_state = TRUE heads_up = "You attack for [attackamt] damage." - playsound(computer.loc, 'sound/arcade/hit.ogg', 50, TRUE, extrarange = -3, falloff = 10) + playsound(computer.loc, 'sound/arcade/hit.ogg', 50, TRUE) boss_hp -= attackamt sleep(10) game_check() @@ -116,14 +122,14 @@ var/healamt = 0 //More Spam Prevention. var/healcost = 0 if(pause_state == FALSE) - healamt = rand(6,8)// + rand(0, gamerSkill) + healamt = rand(6,8) + rand(0, gamerSkill) var/maxPointCost = 3 // if(gamerSkillLevel >= SKILL_LEVEL_JOURNEYMAN) // maxPointCost = 2 healcost = rand(1, maxPointCost) pause_state = TRUE heads_up = "You heal for [healamt] damage." - playsound(computer.loc, 'sound/arcade/heal.ogg', 50, TRUE, extrarange = -3, falloff = 10) + playsound(computer.loc, 'sound/arcade/heal.ogg', 50, TRUE) player_hp += healamt player_mp -= healcost sleep(10) @@ -133,10 +139,10 @@ if("Recharge_Power") var/rechargeamt = 0 //As above. if(pause_state == FALSE) - rechargeamt = rand(4,7)// + rand(0, gamerSkill) + rechargeamt = rand(4,7) + rand(0, gamerSkill) pause_state = TRUE heads_up = "You regain [rechargeamt] magic power." - playsound(computer.loc, 'sound/arcade/mana.ogg', 50, TRUE, extrarange = -3, falloff = 10) + playsound(computer.loc, 'sound/arcade/mana.ogg', 50, TRUE) player_mp += rechargeamt sleep(10) game_check() @@ -153,7 +159,7 @@ computer.visible_message("\The [computer] prints out paper.") if(ticket_count >= 1) new /obj/item/stack/arcadeticket((get_turf(computer)), 1) - to_chat(usr, "[computer] dispenses a ticket!") + to_chat(usr, "[src] dispenses a ticket!") ticket_count -= 1 printer.stored_paper -= 1 else diff --git a/code/modules/modular_computers/file_system/programs/atmosscan.dm b/code/modules/modular_computers/file_system/programs/atmosscan.dm index c4b9951838..7c491712fe 100644 --- a/code/modules/modular_computers/file_system/programs/atmosscan.dm +++ b/code/modules/modular_computers/file_system/programs/atmosscan.dm @@ -5,6 +5,7 @@ extended_desc = "A small built-in sensor reads out the atmospheric conditions around the device." size = 4 tgui_id = "NtosAtmos" + program_icon = "thermometer-half" /datum/computer_file/program/atmosscan/run_program(mob/living/user) . = ..() @@ -39,5 +40,6 @@ return data /datum/computer_file/program/atmosscan/ui_act(action, list/params) - if(..()) - return TRUE + . = ..() + if(.) + return diff --git a/code/modules/modular_computers/file_system/programs/borg_monitor.dm b/code/modules/modular_computers/file_system/programs/borg_monitor.dm index 13caab27ef..46e1f89ee4 100644 --- a/code/modules/modular_computers/file_system/programs/borg_monitor.dm +++ b/code/modules/modular_computers/file_system/programs/borg_monitor.dm @@ -8,6 +8,7 @@ transfer_access = ACCESS_ROBOTICS size = 5 tgui_id = "NtosCyborgRemoteMonitor" + program_icon = "project-diagram" /datum/computer_file/program/borg_monitor/ui_data(mob/user) var/list/data = get_header_data() @@ -43,7 +44,8 @@ return data /datum/computer_file/program/borg_monitor/ui_act(action, params) - if(..()) + . = ..() + if(.) return switch(action) @@ -54,10 +56,14 @@ var/ID = checkID() if(!ID) return + if(R.stat == DEAD) //Dead borgs will listen to you no longer + to_chat(usr, "Error -- Could not open a connection to unit:[R]") var/message = stripped_input(usr, message = "Enter message to be sent to remote cyborg.", title = "Send Message") if(!message) return to_chat(R, "

    Message from [ID] -- \"[message]\"
    ") + to_chat(usr, "Message sent to [R]: [message]") + R.logevent("Message from [ID] -- \"[message]\"") SEND_SOUND(R, 'sound/machines/twobeep_high.ogg') if(R.connected_ai) to_chat(R.connected_ai, "

    Message from [ID] to [R] -- \"[message]\"
    ") diff --git a/code/modules/modular_computers/file_system/programs/bounty_board.dm b/code/modules/modular_computers/file_system/programs/bounty_board.dm index 2e7d3cc87f..9c42a28a9b 100644 --- a/code/modules/modular_computers/file_system/programs/bounty_board.dm +++ b/code/modules/modular_computers/file_system/programs/bounty_board.dm @@ -44,7 +44,8 @@ return data /datum/computer_file/program/bounty_board/ui_act(action, list/params) - if(..()) + . = ..() + if(.) return var/current_ref_num = params["request"] var/current_app_num = params["applicant"] diff --git a/code/modules/modular_computers/file_system/programs/budgetordering.dm b/code/modules/modular_computers/file_system/programs/budgetordering.dm new file mode 100644 index 0000000000..65ff97dd1b --- /dev/null +++ b/code/modules/modular_computers/file_system/programs/budgetordering.dm @@ -0,0 +1,281 @@ +/datum/computer_file/program/budgetorders + filename = "orderapp" + filedesc = "Nanotrasen Internal Requisition Network (NIRN)" + program_icon_state = "request" + extended_desc = "A request network that utilizes the Nanotrasen Ordering network to purchase supplies using a department budget account." + requires_ntnet = TRUE + transfer_access = ACCESS_HEADS + usage_flags = PROGRAM_LAPTOP | PROGRAM_TABLET + size = 20 + tgui_id = "NtosCargo" + ///Are you actually placing orders with it? + var/requestonly = TRUE + ///Can the tablet see or buy illegal stuff? + var/contraband = FALSE + ///Is it being bought from a personal account, or is it being done via a budget/cargo? + var/self_paid = FALSE + ///Can this console approve purchase requests? + var/can_approve_requests = FALSE + ///What do we say when the shuttle moves with living beings on it. + var/safety_warning = "For safety reasons, the automated supply shuttle \ + cannot transport live organisms, human remains, classified nuclear weaponry, \ + homing beacons or machinery housing any form of artificial intelligence." + ///If you're being raided by pirates, what do you tell the crew? + var/blockade_warning = "Bluespace instability detected. Shuttle movement impossible." + +/datum/computer_file/program/budgetorders/proc/get_export_categories() + . = EXPORT_CARGO + +/datum/computer_file/program/budgetorders/proc/is_visible_pack(mob/user, paccess_to_check, list/access, contraband) + if(issilicon(user)) //Borgs can't buy things. + return FALSE + if(computer.obj_flags & EMAGGED) + return TRUE + else if(contraband) //Hide contrband when non-emagged. + return FALSE + if(!paccess_to_check) // No required_access, allow it. + return TRUE + if(IsAdminGhost(user)) + return TRUE + + //Aquire access from the inserted ID card. + if(!length(access)) + var/obj/item/card/id/D + var/obj/item/computer_hardware/card_slot/card_slot + if(computer) + card_slot = computer.all_components[MC_CARD] + D = card_slot?.GetID() + if(!D) + return FALSE + access = D.GetAccess() + + if(paccess_to_check in access) + return TRUE + + return FALSE + +/datum/computer_file/program/budgetorders/ui_data() + . = ..() + var/list/data = get_header_data() + data["location"] = SSshuttle.supply.getStatusText() + var/datum/bank_account/buyer = SSeconomy.get_dep_account(ACCOUNT_CAR) + var/obj/item/computer_hardware/card_slot/card_slot = computer.all_components[MC_CARD] + var/obj/item/card/id/id_card = card_slot?.GetID() + if(id_card?.registered_account) + if(ACCESS_HEADS in id_card.access) + requestonly = FALSE + buyer = SSeconomy.get_dep_account(id_card.registered_account.account_job.paycheck_department) + can_approve_requests = TRUE + else + requestonly = TRUE + can_approve_requests = FALSE + else + requestonly = TRUE + if(buyer) + data["points"] = buyer.account_balance + +//Otherwise static data, that is being applied in ui_data as the crates visible and buyable are not static, and are determined by inserted ID. + data["requestonly"] = requestonly + data["supplies"] = list() + for(var/pack in SSshuttle.supply_packs) + var/datum/supply_pack/P = SSshuttle.supply_packs[pack] + // todo: replace to P.access_view + if(!is_visible_pack(usr, P.access , null, P.contraband) || P.hidden) + continue + if(!data["supplies"][P.group]) + data["supplies"][P.group] = list( + "name" = P.group, + "packs" = list() + ) + if((P.hidden && (P.contraband && !contraband) || (P.special && !P.special_enabled) || P.DropPodOnly)) + continue + data["supplies"][P.group]["packs"] += list(list( + "name" = P.name, + "cost" = P.cost, + "id" = pack, + "desc" = P.desc || P.name, // If there is a description, use it. Otherwise use the pack's name. + "goody" = P.goody, + "access" = P.access + )) + +//Data regarding the User's capability to buy things. + data["has_id"] = id_card + data["away"] = SSshuttle.supply.getDockedId() == "supply_away" + data["self_paid"] = self_paid + data["docked"] = SSshuttle.supply.mode == SHUTTLE_IDLE + data["loan"] = !!SSshuttle.shuttle_loan + data["loan_dispatched"] = SSshuttle.shuttle_loan && SSshuttle.shuttle_loan.dispatched + data["can_send"] = FALSE //There is no situation where I want the app to be able to send the shuttle AWAY from the station, but conversely is fine. + data["can_approve_requests"] = can_approve_requests + data["app_cost"] = TRUE + var/message = "Remember to stamp and send back the supply manifests." + if(SSshuttle.centcom_message) + message = SSshuttle.centcom_message + if(SSshuttle.supplyBlocked) + message = blockade_warning + data["message"] = message + data["cart"] = list() + for(var/datum/supply_order/SO in SSshuttle.shoppinglist) + data["cart"] += list(list( + "object" = SO.pack.name, + "cost" = SO.pack.cost, + "id" = SO.id, + "orderer" = SO.orderer, + "paid" = !isnull(SO.paying_account) //paid by requester + )) + + data["requests"] = list() + for(var/datum/supply_order/SO in SSshuttle.requestlist) + data["requests"] += list(list( + "object" = SO.pack.name, + "cost" = SO.pack.cost, + "orderer" = SO.orderer, + "reason" = SO.reason, + "id" = SO.id + )) + + return data + +/datum/computer_file/program/budgetorders/ui_act(action, params, datum/tgui/ui) + if(..()) + return + var/obj/item/computer_hardware/card_slot/card_slot = computer.all_components[MC_CARD] + switch(action) + if("send") + if(!SSshuttle.supply.canMove()) + computer.say(safety_warning) + return + if(SSshuttle.supplyBlocked) + computer.say(blockade_warning) + return + if(SSshuttle.supply.getDockedId() == "supply_home") + SSshuttle.supply.export_categories = get_export_categories() + SSshuttle.moveShuttle("supply", "supply_away", TRUE) + computer.say("The supply shuttle is departing.") + computer.investigate_log("[key_name(usr)] sent the supply shuttle away.", INVESTIGATE_CARGO) + else + computer.investigate_log("[key_name(usr)] called the supply shuttle.", INVESTIGATE_CARGO) + computer.say("The supply shuttle has been called and will arrive in [SSshuttle.supply.timeLeft(600)] minutes.") + SSshuttle.moveShuttle("supply", "supply_home", TRUE) + . = TRUE + if("loan") + if(!SSshuttle.shuttle_loan) + return + if(SSshuttle.supplyBlocked) + computer.say(blockade_warning) + return + else if(SSshuttle.supply.mode != SHUTTLE_IDLE) + return + else if(SSshuttle.supply.getDockedId() != "supply_away") + return + else + SSshuttle.shuttle_loan.loan_shuttle() + computer.say("The supply shuttle has been loaned to CentCom.") + computer.investigate_log("[key_name(usr)] accepted a shuttle loan event.", INVESTIGATE_CARGO) + log_game("[key_name(usr)] accepted a shuttle loan event.") + . = TRUE + if("add") + var/id = text2path(params["id"]) + var/datum/supply_pack/pack = SSshuttle.supply_packs[id] + if(!istype(pack)) + return + if((pack.hidden && (pack.contraband && !contraband) || pack.DropPodOnly)) + return + + var/name = "*None Provided*" + var/rank = "*None Provided*" + var/ckey = usr.ckey + if(ishuman(usr)) + var/mob/living/carbon/human/H = usr + name = H.get_authentification_name() + rank = H.get_assignment(hand_first = TRUE) + else if(issilicon(usr)) + name = usr.real_name + rank = "Silicon" + + var/datum/bank_account/account + if(self_paid) + var/mob/living/carbon/human/H = usr + var/obj/item/card/id/id_card = H.get_idcard(TRUE) + if(!istype(id_card)) + computer.say("No ID card detected.") + return + if(istype(id_card, /obj/item/card/id/departmental_budget)) + computer.say("The [src] rejects [id_card].") + return + account = id_card.registered_account + if(!istype(account)) + computer.say("Invalid bank account.") + return + + var/reason = "" + if((requestonly && !self_paid) || !(card_slot?.GetID())) + reason = stripped_input("Reason:", name, "") + if(isnull(reason) || ..()) + return + + if(pack.goody && !self_paid) + playsound(src, 'sound/machines/buzz-sigh.ogg', 50, FALSE) + computer.say("ERROR: Small crates may only be purchased by private accounts.") + return + + if(!self_paid && ishuman(usr) && !account) + var/obj/item/card/id/id_card = card_slot?.GetID() + account = SSeconomy.get_dep_account(id_card?.registered_account?.account_job.paycheck_department) + + var/turf/T = get_turf(src) + var/datum/supply_order/SO = new(pack, name, rank, ckey, reason, account) + SO.generateRequisition(T) + if((requestonly && !self_paid) || !(card_slot?.GetID())) + SSshuttle.requestlist += SO + else + SSshuttle.shoppinglist += SO + if(self_paid) + computer.say("Order processed. The price will be charged to [account.account_holder]'s bank account on delivery.") + . = TRUE + if("remove") + var/id = text2num(params["id"]) + for(var/datum/supply_order/SO in SSshuttle.shoppinglist) + if(SO.id == id) + SSshuttle.shoppinglist -= SO + . = TRUE + break + if("clear") + SSshuttle.shoppinglist.Cut() + . = TRUE + if("approve") + var/id = text2num(params["id"]) + for(var/datum/supply_order/SO in SSshuttle.requestlist) + if(SO.id == id) + var/obj/item/card/id/id_card = card_slot?.GetID() + if(id_card && id_card?.registered_account) + SO.paying_account = SSeconomy.get_dep_account(id_card?.registered_account?.account_job.paycheck_department) + SSshuttle.requestlist -= SO + SSshuttle.shoppinglist += SO + . = TRUE + break + if("deny") + var/id = text2num(params["id"]) + for(var/datum/supply_order/SO in SSshuttle.requestlist) + if(SO.id == id) + SSshuttle.requestlist -= SO + . = TRUE + break + if("denyall") + SSshuttle.requestlist.Cut() + . = TRUE + if("toggleprivate") + self_paid = !self_paid + . = TRUE + if(.) + post_signal("supply") + +/datum/computer_file/program/budgetorders/proc/post_signal(command) + + var/datum/radio_frequency/frequency = SSradio.return_frequency(FREQ_STATUS_DISPLAYS) + + if(!frequency) + return + + var/datum/signal/status_signal = new(list("command" = command)) + frequency.post_signal(src, status_signal) diff --git a/code/modules/modular_computers/file_system/programs/card.dm b/code/modules/modular_computers/file_system/programs/card.dm index 6d45914add..65bb5f2343 100644 --- a/code/modules/modular_computers/file_system/programs/card.dm +++ b/code/modules/modular_computers/file_system/programs/card.dm @@ -15,6 +15,7 @@ requires_ntnet = 0 size = 8 tgui_id = "NtosCard" + program_icon = "id-card" var/is_centcom = FALSE var/minor = FALSE @@ -94,8 +95,9 @@ return FALSE /datum/computer_file/program/card_mod/ui_act(action, params) - if(..()) - return TRUE + . = ..() + if(.) + return var/obj/item/computer_hardware/card_slot/card_slot var/obj/item/computer_hardware/card_slot/card_slot2 @@ -130,7 +132,7 @@ if(!authenticated) return var/contents = {"

    Access Report

    - Prepared By: [user_id_card && user_id_card.registered_name ? user_id_card.registered_name : "Unknown"]
    + Prepared By: [user_id_card?.registered_name ? user_id_card.registered_name : "Unknown"]
    For: [target_id_card.registered_name ? target_id_card.registered_name : "Unregistered"]

    Assignment: [target_id_card.assignment]
    @@ -320,32 +322,31 @@ /datum/computer_file/program/card_mod/ui_data(mob/user) var/list/data = get_header_data() + data["station_name"] = station_name() + var/obj/item/computer_hardware/card_slot/card_slot2 var/obj/item/computer_hardware/printer/printer if(computer) card_slot2 = computer.all_components[MC_CARD2] printer = computer.all_components[MC_PRINT] - - data["station_name"] = station_name() - - if(computer) data["have_id_slot"] = !!(card_slot2) - data["have_printer"] = !!printer + data["have_printer"] = !!(printer) else data["have_id_slot"] = FALSE data["have_printer"] = FALSE data["authenticated"] = authenticated + if(!card_slot2) + return data //We're just gonna error out on the js side at this point anyway - if(computer) - var/obj/item/card/id/id_card = card_slot2.stored_card - data["has_id"] = !!id_card - data["id_name"] = id_card ? id_card.name : "-----" - if(id_card) - data["id_rank"] = id_card.assignment ? id_card.assignment : "Unassigned" - data["id_owner"] = id_card.registered_name ? id_card.registered_name : "-----" - data["access_on_card"] = id_card.access + var/obj/item/card/id/id_card = card_slot2.stored_card + data["has_id"] = !!id_card + data["id_name"] = id_card ? id_card.name : "-----" + if(id_card) + data["id_rank"] = id_card.assignment ? id_card.assignment : "Unassigned" + data["id_owner"] = id_card.registered_name ? id_card.registered_name : "-----" + data["access_on_card"] = id_card.access return data diff --git a/code/modules/modular_computers/file_system/programs/cargoship.dm b/code/modules/modular_computers/file_system/programs/cargoship.dm index db8d6d9f82..89a3b3247d 100644 --- a/code/modules/modular_computers/file_system/programs/cargoship.dm +++ b/code/modules/modular_computers/file_system/programs/cargoship.dm @@ -5,6 +5,7 @@ extended_desc = "A combination printer/scanner app that enables modular computers to print barcodes for easy scanning and shipping." size = 6 tgui_id = "NtosShipping" + program_icon = "tags" ///Account used for creating barcodes. var/datum/bank_account/payments_acc ///The amount which the tagger will receive for the sale. @@ -19,14 +20,15 @@ data["has_id_slot"] = !!card_slot data["has_printer"] = !!printer data["paperamt"] = printer ? "[printer.stored_paper] / [printer.max_paper]" : null - data["card_owner"] = card_slot && card_slot.stored_card ? id_card.registered_name : "No Card Inserted." + data["card_owner"] = card_slot?.stored_card ? id_card.registered_name : "No Card Inserted." data["current_user"] = payments_acc ? payments_acc.account_holder : null data["barcode_split"] = percent_cut return data /datum/computer_file/program/shipping/ui_act(action, list/params) - if(..()) - return TRUE + . = ..() + if(.) + return if(!computer) return @@ -40,7 +42,7 @@ switch(action) if("ejectid") if(id_card) - card_slot.try_eject(TRUE, usr) + card_slot.try_eject(usr, TRUE) if("selectid") if(!id_card) return diff --git a/code/modules/modular_computers/file_system/programs/configurator.dm b/code/modules/modular_computers/file_system/programs/configurator.dm index fae06544d5..cf5d950c0f 100644 --- a/code/modules/modular_computers/file_system/programs/configurator.dm +++ b/code/modules/modular_computers/file_system/programs/configurator.dm @@ -13,6 +13,7 @@ available_on_ntnet = 0 requires_ntnet = 0 tgui_id = "NtosConfiguration" + program_icon = "cog" var/obj/item/modular_computer/movable = null @@ -34,11 +35,11 @@ data["disk_used"] = hard_drive.used_capacity data["power_usage"] = movable.last_power_usage data["battery_exists"] = battery_module ? 1 : 0 - if(battery_module && battery_module.battery) + if(battery_module?.battery) data["battery_rating"] = battery_module.battery.maxcharge data["battery_percent"] = round(battery_module.battery.percent()) - if(battery_module && battery_module.battery) + if(battery_module?.battery) data["battery"] = list("max" = battery_module.battery.maxcharge, "charge" = round(battery_module.battery.charge)) var/list/all_entries[0] @@ -57,7 +58,8 @@ /datum/computer_file/program/computerconfig/ui_act(action,params) - if(..()) + . = ..() + if(.) return switch(action) if("PC_toggle_component") diff --git a/code/modules/modular_computers/file_system/programs/crewmanifest.dm b/code/modules/modular_computers/file_system/programs/crewmanifest.dm index 4f2688d8f1..debe87259d 100644 --- a/code/modules/modular_computers/file_system/programs/crewmanifest.dm +++ b/code/modules/modular_computers/file_system/programs/crewmanifest.dm @@ -7,6 +7,7 @@ requires_ntnet = TRUE size = 4 tgui_id = "NtosCrewManifest" + program_icon = "clipboard-list" /datum/computer_file/program/crew_manifest/ui_static_data(mob/user) var/list/data = list() @@ -27,7 +28,8 @@ return data /datum/computer_file/program/crew_manifest/ui_act(action, params, datum/tgui/ui) - if(..()) + . = ..() + if(.) return var/obj/item/computer_hardware/printer/printer diff --git a/code/modules/modular_computers/file_system/programs/file_browser.dm b/code/modules/modular_computers/file_system/programs/file_browser.dm index aba826fce8..97a71496ea 100644 --- a/code/modules/modular_computers/file_system/programs/file_browser.dm +++ b/code/modules/modular_computers/file_system/programs/file_browser.dm @@ -8,12 +8,14 @@ available_on_ntnet = FALSE undeletable = TRUE tgui_id = "NtosFileManager" + program_icon = "folder" var/open_file var/error /datum/computer_file/program/filemanager/ui_act(action, params) - if(..()) + . = ..() + if(.) return var/obj/item/computer_hardware/hard_drive/HDD = computer.all_components[MC_HDD] @@ -65,6 +67,13 @@ var/datum/computer_file/C = F.clone(FALSE) HDD.store_file(C) return TRUE + if("PRG_togglesilence") + if(!HDD) + return + var/datum/computer_file/program/binary = HDD.find_file_by_name(params["name"]) + if(!binary || !istype(binary)) + return + binary.alert_silenced = !binary.alert_silenced /datum/computer_file/program/filemanager/ui_data(mob/user) var/list/data = get_header_data() @@ -78,11 +87,19 @@ else var/list/files = list() for(var/datum/computer_file/F in HDD.stored_files) + var/noisy = FALSE + var/silenced = FALSE + var/datum/computer_file/program/binary = F + if(istype(binary)) + noisy = binary.alert_able + silenced = binary.alert_silenced files += list(list( "name" = F.filename, "type" = F.filetype, "size" = F.size, - "undeletable" = F.undeletable + "undeletable" = F.undeletable, + "alert_able" = noisy, + "alert_silenced" = silenced )) data["files"] = files if(RHDD) diff --git a/code/modules/modular_computers/file_system/programs/jobmanagement.dm b/code/modules/modular_computers/file_system/programs/jobmanagement.dm index b88b793b66..3f21d2cf2c 100644 --- a/code/modules/modular_computers/file_system/programs/jobmanagement.dm +++ b/code/modules/modular_computers/file_system/programs/jobmanagement.dm @@ -7,6 +7,7 @@ requires_ntnet = TRUE size = 4 tgui_id = "NtosJobManager" + program_icon = "address-book" var/change_position_cooldown = 30 //Jobs you cannot open new positions for @@ -49,17 +50,14 @@ return FALSE /datum/computer_file/program/job_management/ui_act(action, params, datum/tgui/ui) - if(..()) + . = ..() + if(.) return - var/authed = FALSE - var/mob/user = usr - var/obj/item/card/id/user_id = user.get_idcard() - if(user_id) - if(ACCESS_CHANGE_IDS in user_id.access) - authed = TRUE + var/obj/item/computer_hardware/card_slot/card_slot = computer.all_components[MC_CARD] + var/obj/item/card/id/user_id = card_slot?.stored_card - if(!authed) + if(!user_id || !(ACCESS_CHANGE_IDS in user_id.access)) return switch(action) @@ -107,10 +105,10 @@ var/list/data = get_header_data() var/authed = FALSE - var/obj/item/card/id/user_id = user.get_idcard(FALSE) - if(user_id) - if(ACCESS_CHANGE_IDS in user_id.access) - authed = TRUE + var/obj/item/computer_hardware/card_slot/card_slot = computer.all_components[MC_CARD] + var/obj/item/card/id/user_id = card_slot?.stored_card + if(user_id && (ACCESS_CHANGE_IDS in user_id.access)) + authed = TRUE data["authed"] = authed diff --git a/code/modules/modular_computers/file_system/programs/ntdownloader.dm b/code/modules/modular_computers/file_system/programs/ntdownloader.dm index 8fbcfd0b01..f3fa6df2b3 100644 --- a/code/modules/modular_computers/file_system/programs/ntdownloader.dm +++ b/code/modules/modular_computers/file_system/programs/ntdownloader.dm @@ -11,10 +11,11 @@ available_on_ntnet = FALSE ui_header = "downloader_finished.gif" tgui_id = "NtosNetDownloader" + program_icon = "download" var/datum/computer_file/program/downloaded_file = null - var/hacked_download = 0 - var/download_completion = 0 //GQ of downloaded data. + var/hacked_download = FALSE + var/download_completion = FALSE //GQ of downloaded data. var/download_netspeed = 0 var/downloaderror = "" var/obj/item/modular_computer/my_computer = null @@ -36,33 +37,33 @@ /datum/computer_file/program/ntnetdownload/proc/begin_file_download(filename) if(downloaded_file) - return 0 + return FALSE var/datum/computer_file/program/PRG = SSnetworks.station_network.find_ntnet_file_by_name(filename) if(!PRG || !istype(PRG)) - return 0 + return FALSE // Attempting to download antag only program, but without having emagged/syndicate computer. No. if(PRG.available_on_syndinet && !emagged) - return 0 + return FALSE var/obj/item/computer_hardware/hard_drive/hard_drive = computer.all_components[MC_HDD] if(!computer || !hard_drive || !hard_drive.can_store_file(PRG)) - return 0 + return FALSE ui_header = "downloader_running.gif" if(PRG in main_repo) generate_network_log("Began downloading file [PRG.filename].[PRG.filetype] from NTNet Software Repository.") - hacked_download = 0 + hacked_download = FALSE else if(PRG in antag_repo) generate_network_log("Began downloading file **ENCRYPTED**.[PRG.filetype] from unspecified server.") - hacked_download = 1 + hacked_download = TRUE else generate_network_log("Began downloading file [PRG.filename].[PRG.filetype] from unspecified server.") - hacked_download = 0 + hacked_download = FALSE downloaded_file = PRG.clone() @@ -71,7 +72,7 @@ return generate_network_log("Aborted download of file [hacked_download ? "**ENCRYPTED**" : "[downloaded_file.filename].[downloaded_file.filetype]"].") downloaded_file = null - download_completion = 0 + download_completion = FALSE ui_header = "downloader_finished.gif" /datum/computer_file/program/ntnetdownload/proc/complete_file_download() @@ -83,7 +84,7 @@ // The download failed downloaderror = "I/O ERROR - Unable to save file. Check whether you have enough free space on your hard drive and whether your hard drive is properly connected. If the issue persists contact your system administrator for assistance." downloaded_file = null - download_completion = 0 + download_completion = FALSE ui_header = "downloader_finished.gif" /datum/computer_file/program/ntnetdownload/process_tick() @@ -104,21 +105,22 @@ download_completion += download_netspeed /datum/computer_file/program/ntnetdownload/ui_act(action, params) - if(..()) - return 1 + . = ..() + if(.) + return switch(action) if("PRG_downloadfile") if(!downloaded_file) begin_file_download(params["filename"]) - return 1 + return TRUE if("PRG_reseterror") if(downloaderror) - download_completion = 0 - download_netspeed = 0 + download_completion = FALSE + download_netspeed = FALSE downloaded_file = null downloaderror = "" - return 1 - return 0 + return TRUE + return FALSE /datum/computer_file/program/ntnetdownload/ui_data(mob/user) my_computer = computer @@ -148,7 +150,7 @@ for(var/A in main_repo) var/datum/computer_file/program/P = A // Only those programs our user can run will show in the list - if(!P.can_run(user,transfer = 1, access = access) || hard_drive.find_file_by_name(P.filename)) + if(hard_drive.find_file_by_name(P.filename)) continue all_entries.Add(list(list( "filename" = P.filename, @@ -156,6 +158,7 @@ "fileinfo" = P.extended_desc, "compatibility" = check_compatibility(P), "size" = P.size, + "access" = P.can_run(user,transfer = 1, access = access) ))) data["hackedavailable"] = FALSE if(emagged) // If we are running on emagged computer we have access to some "bonus" software @@ -169,7 +172,9 @@ "filename" = P.filename, "filedesc" = P.filedesc, "fileinfo" = P.extended_desc, + "compatibility" = check_compatibility(P), "size" = P.size, + "access" = TRUE, ))) data["hacked_programs"] = hacked_programs @@ -180,13 +185,13 @@ /datum/computer_file/program/ntnetdownload/proc/check_compatibility(datum/computer_file/program/P) var/hardflag = computer.hardware_flag - if(P && P.is_supported_by_hardware(hardflag,0)) + if(P?.is_supported_by_hardware(hardflag,0)) return "Compatible" return "Incompatible!" /datum/computer_file/program/ntnetdownload/kill_program(forced) abort_file_download() - return ..(forced) + return ..() //////////////////////// //Syndicate Downloader// @@ -199,7 +204,7 @@ filedesc = "Software Download Tool" program_icon_state = "generic" extended_desc = "This program allows downloads of software from shared Syndicate repositories" - requires_ntnet = 0 + requires_ntnet = FALSE ui_header = "downloader_finished.gif" tgui_id = "NtosNetDownloader" emagged = TRUE diff --git a/code/modules/modular_computers/file_system/programs/ntmonitor.dm b/code/modules/modular_computers/file_system/programs/ntmonitor.dm index bbbde14780..63f0b18a74 100644 --- a/code/modules/modular_computers/file_system/programs/ntmonitor.dm +++ b/code/modules/modular_computers/file_system/programs/ntmonitor.dm @@ -1,6 +1,6 @@ /datum/computer_file/program/ntnetmonitor filename = "wirecarp" - filedesc = "WireCarp" //wireshark. + filedesc = "WireCarp" program_icon_state = "comm_monitor" extended_desc = "This program monitors stationwide NTNet network, provides access to logging systems, and allows for configuration changes" size = 12 @@ -8,9 +8,11 @@ required_access = ACCESS_NETWORK //NETWORK CONTROL IS A MORE SECURE PROGRAM. available_on_ntnet = TRUE tgui_id = "NtosNetMonitor" + program_icon = "network-wired" /datum/computer_file/program/ntnetmonitor/ui_act(action, params) - if(..()) + . = ..() + if(.) return switch(action) if("resetIDS") diff --git a/code/modules/modular_computers/file_system/programs/ntnrc_client.dm b/code/modules/modular_computers/file_system/programs/ntnrc_client.dm index f03ff3f8fd..19172f130a 100644 --- a/code/modules/modular_computers/file_system/programs/ntnrc_client.dm +++ b/code/modules/modular_computers/file_system/programs/ntnrc_client.dm @@ -9,6 +9,7 @@ ui_header = "ntnrc_idle.gif" available_on_ntnet = 1 tgui_id = "NtosNetChat" + program_icon = "comment-alt" var/last_message // Used to generate the toolbar icon var/username var/active_channel @@ -20,7 +21,8 @@ username = "DefaultUser[rand(100, 999)]" /datum/computer_file/program/chatclient/ui_act(action, params) - if(..()) + . = ..() + if(.) return var/datum/ntnet_conversation/channel = SSnetworks.station_network.get_chat_channel_by_id(active_channel) @@ -182,7 +184,7 @@ var/list/all_channels = list() for(var/C in SSnetworks.station_network.chat_channels) var/datum/ntnet_conversation/conv = C - if(conv && conv.title) + if(conv?.title) all_channels.Add(list(list( "chan" = conv.title, "id" = conv.id diff --git a/code/modules/modular_computers/file_system/programs/powermonitor.dm b/code/modules/modular_computers/file_system/programs/powermonitor.dm index e87a731a40..78a14ff1ad 100644 --- a/code/modules/modular_computers/file_system/programs/powermonitor.dm +++ b/code/modules/modular_computers/file_system/programs/powermonitor.dm @@ -11,6 +11,7 @@ requires_ntnet = 0 size = 9 tgui_id = "NtosPowerMonitor" + program_icon = "plug" var/has_alert = 0 var/obj/structure/cable/attached_wire @@ -49,7 +50,7 @@ local_apc = null /datum/computer_file/program/power_monitor/proc/get_powernet() //keep in sync with /obj/machinery/computer/monitor's version - if(attached_wire || (local_apc && local_apc.terminal)) + if(attached_wire || (local_apc?.terminal)) return attached_wire ? attached_wire.powernet : local_apc.terminal.powernet return FALSE diff --git a/code/modules/modular_computers/file_system/programs/radar.dm b/code/modules/modular_computers/file_system/programs/radar.dm index 216365d6ea..0bf5eb2118 100644 --- a/code/modules/modular_computers/file_system/programs/radar.dm +++ b/code/modules/modular_computers/file_system/programs/radar.dm @@ -63,7 +63,8 @@ return data /datum/computer_file/program/radar/ui_act(action, params) - if(..()) + . = ..() + if(.) return switch(action) @@ -73,13 +74,13 @@ scan() /** - *Updates tracking information of the selected target. - * - *The track() proc updates the entire set of information about the location - *of the target, including whether the Ntos window should use a pinpointer - *crosshair over the up/down arrows, or none in favor of a rotating arrow - *for far away targets. This information is returned in the form of a list. - * + *Updates tracking information of the selected target. + * + *The track() proc updates the entire set of information about the location + *of the target, including whether the Ntos window should use a pinpointer + *crosshair over the up/down arrows, or none in favor of a rotating arrow + *for far away targets. This information is returned in the form of a list. + * */ /datum/computer_file/program/radar/proc/track() var/atom/movable/signal = find_atom() @@ -115,13 +116,13 @@ return trackinfo /** - * - *Checks the trackability of the selected target. - * - *If the target is on the computer's Z level, or both are on station Z - *levels, and the target isn't untrackable, return TRUE. - *Arguments: - **arg1 is the atom being evaluated. + * + *Checks the trackability of the selected target. + * + *If the target is on the computer's Z level, or both are on station Z + *levels, and the target isn't untrackable, return TRUE. + *Arguments: + **arg1 is the atom being evaluated. */ /datum/computer_file/program/radar/proc/trackable(atom/movable/signal) if(!signal || !computer) @@ -133,30 +134,30 @@ return (there.z == here.z) || (is_station_level(here.z) && is_station_level(there.z)) /** - * - *Runs a scan of all the trackable atoms. - * - *Checks each entry in the GLOB of the specific trackable atoms against - *the track() proc, and fill the objects list with lists containing the - *atoms' names and REFs. The objects list is handed to the tgui screen - *for displaying to, and being selected by, the user. A two second - *sleep is used to delay the scan, both for thematical reasons as well - *as to limit the load players may place on the server using these - *somewhat costly loops. + * + *Runs a scan of all the trackable atoms. + * + *Checks each entry in the GLOB of the specific trackable atoms against + *the track() proc, and fill the objects list with lists containing the + *atoms' names and REFs. The objects list is handed to the tgui screen + *for displaying to, and being selected by, the user. A two second + *sleep is used to delay the scan, both for thematical reasons as well + *as to limit the load players may place on the server using these + *somewhat costly loops. */ /datum/computer_file/program/radar/proc/scan() return /** - * - *Finds the atom in the appropriate list that the `selected` var indicates - * - *The `selected` var holds a REF, which is a string. A mob REF may be - *something like "mob_209". In order to find the actual atom, we need - *to search the appropriate list for the REF string. This is dependant - *on the program (Lifeline uses GLOB.human_list, while Fission360 uses - *GLOB.poi_list), but the result will be the same; evaluate the string and - *return an atom reference. + * + *Finds the atom in the appropriate list that the `selected` var indicates + * + *The `selected` var holds a REF, which is a string. A mob REF may be + *something like "mob_209". In order to find the actual atom, we need + *to search the appropriate list for the REF string. This is dependant + *on the program (Lifeline uses GLOB.human_list, while Fission360 uses + *GLOB.poi_list), but the result will be the same; evaluate the string and + *return an atom reference. */ /datum/computer_file/program/radar/proc/find_atom() return @@ -212,6 +213,7 @@ requires_ntnet = TRUE transfer_access = ACCESS_MEDICAL available_on_ntnet = TRUE + program_icon = "heartbeat" /datum/computer_file/program/radar/lifeline/find_atom() return locate(selected) in GLOB.human_list @@ -228,7 +230,7 @@ var/crewmember_name = "Unknown" if(humanoid.wear_id) var/obj/item/card/id/ID = humanoid.wear_id.GetID() - if(ID && ID.registered_name) + if(ID?.registered_name) crewmember_name = ID.registered_name var/list/crewinfo = list( ref = REF(humanoid), @@ -262,6 +264,7 @@ available_on_ntnet = FALSE available_on_syndinet = TRUE tgui_id = "NtosRadarSyndicate" + program_icon = "bomb" arrowstyle = "ntosradarpointerS.png" pointercolor = "red" diff --git a/code/modules/modular_computers/file_system/programs/robocontrol.dm b/code/modules/modular_computers/file_system/programs/robocontrol.dm index c0b82b9c95..8c41ea6c38 100644 --- a/code/modules/modular_computers/file_system/programs/robocontrol.dm +++ b/code/modules/modular_computers/file_system/programs/robocontrol.dm @@ -1,13 +1,14 @@ /datum/computer_file/program/robocontrol filename = "botkeeper" - filedesc = "Botkeeper" + filedesc = "BotKeeper" program_icon_state = "robot" extended_desc = "A remote controller used for giving basic commands to non-sentient robots." - transfer_access = ACCESS_ROBOTICS + transfer_access = null requires_ntnet = TRUE size = 12 tgui_id = "NtosRoboControl" + program_icon = "robot" ///Number of simple robots on-station. var/botcount = 0 ///Used to find the location of the user for the purposes of summoning robots. @@ -36,7 +37,13 @@ for(var/B in GLOB.bots_list) var/mob/living/simple_animal/bot/Bot = B if(!Bot.on || Bot.z != zlevel || Bot.remote_disabled) //Only non-emagged bots on the same Z-level are detected! - continue //Also, the PDA must have access to the bot type. + continue + else if(computer) //Also, the inserted ID must have access to the bot type + var/obj/item/card/id/id_card = card_slot ? card_slot.stored_card : null + if(!id_card && !Bot.bot_core.allowed(current_user)) + continue + else if(id_card && !Bot.bot_core.check_access(id_card)) + continue var/list/newbot = list("name" = Bot.name, "mode" = Bot.get_mode_ui(), "model" = Bot.model, "locat" = get_area(Bot), "bot_ref" = REF(Bot), "mule_check" = FALSE) if(Bot.bot_type == MULE_BOT) var/mob/living/simple_animal/bot/mulebot/MULE = Bot @@ -53,8 +60,9 @@ return data /datum/computer_file/program/robocontrol/ui_act(action, list/params) - if(..()) - return TRUE + . = ..() + if(.) + return var/obj/item/computer_hardware/card_slot/card_slot var/obj/item/card/id/id_card if(computer) diff --git a/code/modules/modular_computers/file_system/programs/robotact.dm b/code/modules/modular_computers/file_system/programs/robotact.dm new file mode 100644 index 0000000000..b25332d027 --- /dev/null +++ b/code/modules/modular_computers/file_system/programs/robotact.dm @@ -0,0 +1,147 @@ +/datum/computer_file/program/robotact + filename = "robotact" + filedesc = "RoboTact" + extended_desc = "A built-in app for cyborg self-management and diagnostics." + ui_header = "robotact.gif" //DEBUG -- new icon before PR + program_icon_state = "command" + requires_ntnet = FALSE + transfer_access = null + available_on_ntnet = FALSE + unsendable = TRUE + undeletable = TRUE + usage_flags = PROGRAM_TABLET + size = 5 + tgui_id = "NtosRobotact" + program_icon = "terminal" + ///A typed reference to the computer, specifying the borg tablet type + var/obj/item/modular_computer/tablet/integrated/tablet + +/datum/computer_file/program/robotact/Destroy() + tablet = null + return ..() + +/datum/computer_file/program/robotact/run_program(mob/living/user) + if(!istype(computer, /obj/item/modular_computer/tablet/integrated)) + to_chat(user, "A warning flashes across \the [computer]: Device Incompatible.") + return FALSE + . = ..() + if(.) + tablet = computer + if(tablet.device_theme == "syndicate") + program_icon_state = "command-syndicate" + return TRUE + return FALSE + +/datum/computer_file/program/robotact/ui_data(mob/user) + var/list/data = get_header_data() + if(!iscyborg(user)) + return data + var/mob/living/silicon/robot/borgo = tablet.borgo + + data["name"] = borgo.name + data["designation"] = borgo.designation //Borgo module type + data["masterAI"] = borgo.connected_ai //Master AI + + var/charge = 0 + var/maxcharge = 1 + if(borgo.cell) + charge = borgo.cell.charge + maxcharge = borgo.cell.maxcharge + data["charge"] = charge //Current cell charge + data["maxcharge"] = maxcharge //Cell max charge + data["integrity"] = ((borgo.health + 100) / 2) //Borgo health, as percentage + data["lampIntensity"] = borgo.lamp_intensity //Borgo lamp power setting + data["sensors"] = "[borgo.sensors_on?"ACTIVE":"DISABLED"]" + data["printerPictures"] = borgo.connected_ai? borgo.connected_ai.aicamera.stored.len : borgo.aicamera.stored.len //Number of pictures taken, synced to AI if available + data["printerToner"] = borgo.toner //amount of toner + data["printerTonerMax"] = borgo.tonermax //It's a variable, might as well use it + data["thrustersInstalled"] = borgo.ionpulse //If we have a thruster uprade + data["thrustersStatus"] = "[borgo.ionpulse_on?"ACTIVE":"DISABLED"]" //Feedback for thruster status + + //DEBUG -- Cover, TRUE for locked + data["cover"] = "[borgo.locked? "LOCKED":"UNLOCKED"]" + //Ability to move. FAULT if lockdown wire is cut, DISABLED if borg locked, ENABLED otherwise + data["locomotion"] = "[borgo.wires.is_cut(WIRE_LOCKDOWN)?"FAULT":"[borgo.locked_down?"DISABLED":"ENABLED"]"]" + //Module wire. FAULT if cut, NOMINAL otherwise + data["wireModule"] = "[borgo.wires.is_cut(WIRE_RESET_MODULE)?"FAULT":"NOMINAL"]" + //DEBUG -- Camera(net) wire. FAULT if cut (or no cameranet camera), DISABLED if pulse-disabled, NOMINAL otherwise + data["wireCamera"] = "[!borgo.builtInCamera || borgo.wires.is_cut(WIRE_CAMERA)?"FAULT":"[borgo.builtInCamera.can_use()?"NOMINAL":"DISABLED"]"]" + //AI wire. FAULT if wire is cut, CONNECTED if connected to AI, READY otherwise + data["wireAI"] = "[borgo.wires.is_cut(WIRE_AI)?"FAULT":"[borgo.connected_ai?"CONNECTED":"READY"]"]" + //Law sync wire. FAULT if cut, NOMINAL otherwise + data["wireLaw"] = "[borgo.wires.is_cut(WIRE_LAWSYNC)?"FAULT":"NOMINAL"]" + + return data + +/datum/computer_file/program/robotact/ui_static_data(mob/user) + var/list/data = list() + if(!iscyborg(user)) + return data + var/mob/living/silicon/robot/borgo = user + + data["Laws"] = borgo.laws.get_law_list(TRUE, TRUE, FALSE) + data["borgLog"] = tablet.borglog + data["borgUpgrades"] = borgo.upgrades + return data + +/datum/computer_file/program/robotact/ui_act(action, params) + . = ..() + if(.) + return + + var/mob/living/silicon/robot/borgo = tablet.borgo + + switch(action) + if("coverunlock") + if(borgo.locked) + borgo.locked = FALSE + borgo.update_icons() + if(borgo.emagged) + borgo.logevent("ChÃ¥vÃis cover lock has been [borgo.locked ? "engaged" : "released"]") //"The cover interface glitches out for a split second" + else + borgo.logevent("Chassis cover lock has been [borgo.locked ? "engaged" : "released"]") + + if("lawchannel") + borgo.set_autosay() + + if("lawstate") + borgo.checklaws() + + if("alertPower") + if(borgo.stat == CONSCIOUS) + if(!borgo.cell || !borgo.cell.charge) + borgo.visible_message("The power warning light on [borgo] flashes urgently.", \ + "You announce you are operating in low power mode.") + playsound(borgo, 'sound/machines/buzz-two.ogg', 50, FALSE) + + if("toggleSensors") + borgo.toggle_sensors() + + if("viewImage") + if(borgo.connected_ai) + borgo.connected_ai.aicamera?.viewpictures(usr) + else + borgo.aicamera?.viewpictures(usr) + + if("printImage") + var/obj/item/camera/siliconcam/robot_camera/borgcam = borgo.aicamera + borgcam?.borgprint(usr) + + if("toggleThrusters") + borgo.toggle_ionpulse() + + if("lampIntensity") + borgo.lamp_intensity = params["ref"] + borgo.toggle_headlamp(FALSE, TRUE) + +/** + * Forces a full update of the UI, if currently open. + * + * Forces an update that includes refreshing ui_static_data. Called by + * law changes and borg log additions. + */ +/datum/computer_file/program/robotact/proc/force_full_update() + if(tablet) + var/datum/tgui/active_ui = SStgui.get_open_ui(tablet.borgo, src) + if(active_ui) + active_ui.send_full_update() diff --git a/code/modules/modular_computers/file_system/programs/secureye.dm b/code/modules/modular_computers/file_system/programs/secureye.dm new file mode 100644 index 0000000000..78e72640ed --- /dev/null +++ b/code/modules/modular_computers/file_system/programs/secureye.dm @@ -0,0 +1,195 @@ +#define DEFAULT_MAP_SIZE 15 + +/datum/computer_file/program/secureye + filename = "secureye" + filedesc = "SecurEye" + ui_header = "borg_mon.gif" + program_icon_state = "generic" + extended_desc = "This program allows access to standard security camera networks." + requires_ntnet = TRUE + transfer_access = ACCESS_SECURITY + usage_flags = PROGRAM_CONSOLE | PROGRAM_LAPTOP + size = 5 + tgui_id = "NtosSecurEye" + program_icon = "eye" + + var/list/network = list("ss13") + var/obj/machinery/camera/active_camera + /// The turf where the camera was last updated. + var/turf/last_camera_turf + var/list/concurrent_users = list() + + // Stuff needed to render the map + var/map_name + var/obj/screen/map_view/cam_screen + /// All the plane masters that need to be applied. + var/list/cam_plane_masters + var/obj/screen/background/cam_background + +/datum/computer_file/program/secureye/New() + . = ..() + // Map name has to start and end with an A-Z character, + // and definitely NOT with a square bracket or even a number. + map_name = "camera_console_[REF(src)]_map" + // Convert networks to lowercase + for(var/i in network) + network -= i + network += lowertext(i) + // Initialize map objects + cam_screen = new + cam_screen.name = "screen" + cam_screen.assigned_map = map_name + cam_screen.del_on_map_removal = FALSE + cam_screen.screen_loc = "[map_name]:1,1" + cam_plane_masters = list() + for(var/plane in subtypesof(/obj/screen/plane_master)) + var/obj/screen/instance = new plane() + instance.assigned_map = map_name + instance.del_on_map_removal = FALSE + instance.screen_loc = "[map_name]:CENTER" + cam_plane_masters += instance + cam_background = new + cam_background.assigned_map = map_name + cam_background.del_on_map_removal = FALSE + +/datum/computer_file/program/secureye/Destroy() + qdel(cam_screen) + QDEL_LIST(cam_plane_masters) + qdel(cam_background) + return ..() + +/datum/computer_file/program/secureye/ui_interact(mob/user, datum/tgui/ui) + // Update UI + ui = SStgui.try_update_ui(user, src, ui) + + // Update the camera, showing static if necessary and updating data if the location has moved. + update_active_camera_screen() + + if(!ui) + var/user_ref = REF(user) + var/is_living = isliving(user) + // Ghosts shouldn't count towards concurrent users, which produces + // an audible terminal_on click. + if(is_living) + concurrent_users += user_ref + // Register map objects + user.client.register_map_obj(cam_screen) + for(var/plane in cam_plane_masters) + user.client.register_map_obj(plane) + user.client.register_map_obj(cam_background) + return ..() + +/datum/computer_file/program/secureye/ui_data() + var/list/data = get_header_data() + data["network"] = network + data["activeCamera"] = null + if(active_camera) + data["activeCamera"] = list( + name = active_camera.c_tag, + status = active_camera.status, + ) + return data + +/datum/computer_file/program/secureye/ui_static_data() + var/list/data = list() + data["mapRef"] = map_name + var/list/cameras = get_available_cameras() + data["cameras"] = list() + for(var/i in cameras) + var/obj/machinery/camera/C = cameras[i] + data["cameras"] += list(list( + name = C.c_tag, + )) + + return data + +/datum/computer_file/program/secureye/ui_act(action, params) + . = ..() + if(.) + return + + if(action == "switch_camera") + var/c_tag = params["name"] + var/list/cameras = get_available_cameras() + var/obj/machinery/camera/selected_camera = cameras[c_tag] + active_camera = selected_camera + playsound(src, get_sfx("terminal_type"), 25, FALSE) + + if(!selected_camera) + return TRUE + + update_active_camera_screen() + + return TRUE + +/datum/computer_file/program/secureye/ui_close(mob/user) + . = ..() + var/user_ref = REF(user) + var/is_living = isliving(user) + // Living creature or not, we remove you anyway. + concurrent_users -= user_ref + // Unregister map objects + user.client.clear_map(map_name) + // Turn off the console + if(length(concurrent_users) == 0 && is_living) + active_camera = null + playsound(src, 'sound/machines/terminal_off.ogg', 25, FALSE) + +/datum/computer_file/program/secureye/proc/update_active_camera_screen() + // Show static if can't use the camera + if(!active_camera?.can_use()) + show_camera_static() + return + + var/list/visible_turfs = list() + + // Is this camera located in or attached to a living thing? If so, assume the camera's loc is the living thing. + var/cam_location = isliving(active_camera.loc) ? active_camera.loc : active_camera + + // If we're not forcing an update for some reason and the cameras are in the same location, + // we don't need to update anything. + // Most security cameras will end here as they're not moving. + var/newturf = get_turf(cam_location) + if(last_camera_turf == newturf) + return + + // Cameras that get here are moving, and are likely attached to some moving atom such as cyborgs. + last_camera_turf = get_turf(cam_location) + + var/list/visible_things = active_camera.isXRay() ? range(active_camera.view_range, cam_location) : view(active_camera.view_range, cam_location) + + for(var/turf/visible_turf in visible_things) + visible_turfs += visible_turf + + var/list/bbox = get_bbox_of_atoms(visible_turfs) + var/size_x = bbox[3] - bbox[1] + 1 + var/size_y = bbox[4] - bbox[2] + 1 + + cam_screen.vis_contents = visible_turfs + cam_background.icon_state = "clear" + cam_background.fill_rect(1, 1, size_x, size_y) + +/datum/computer_file/program/secureye/proc/show_camera_static() + cam_screen.vis_contents.Cut() + cam_background.icon_state = "scanline2" + cam_background.fill_rect(1, 1, DEFAULT_MAP_SIZE, DEFAULT_MAP_SIZE) + +// Returns the list of cameras accessible from this computer +/datum/computer_file/program/secureye/proc/get_available_cameras() + var/list/L = list() + for (var/obj/machinery/camera/cam in GLOB.cameranet.cameras) + if(!is_station_level(cam.z))//Only show station cameras. + continue + L.Add(cam) + var/list/camlist = list() + for(var/obj/machinery/camera/cam in L) + if(!cam.network) + stack_trace("Camera in a cameranet has no camera network") + continue + if(!(islist(cam.network))) + stack_trace("Camera in a cameranet has a non-list camera network") + continue + var/list/tempnetwork = cam.network & network + if(tempnetwork.len) + camlist["[cam.c_tag]"] = cam + return camlist diff --git a/code/modules/modular_computers/file_system/programs/sm_monitor.dm b/code/modules/modular_computers/file_system/programs/sm_monitor.dm index e4cf590930..7ef2f7416a 100644 --- a/code/modules/modular_computers/file_system/programs/sm_monitor.dm +++ b/code/modules/modular_computers/file_system/programs/sm_monitor.dm @@ -8,10 +8,16 @@ transfer_access = ACCESS_CONSTRUCTION size = 5 tgui_id = "NtosSupermatterMonitor" + program_icon = "radiation" + alert_able = TRUE var/last_status = SUPERMATTER_INACTIVE var/list/supermatters var/obj/machinery/power/supermatter_crystal/active // Currently selected supermatter crystal. +/datum/computer_file/program/supermatter_monitor/Destroy() + clear_signals() + active = null + return ..() /datum/computer_file/program/supermatter_monitor/process_tick() ..() @@ -25,10 +31,11 @@ /datum/computer_file/program/supermatter_monitor/run_program(mob/living/user) . = ..(user) + if(!(active in GLOB.machines)) + active = null refresh() /datum/computer_file/program/supermatter_monitor/kill_program(forced = FALSE) - active = null supermatters = null ..() @@ -52,6 +59,58 @@ for(var/obj/machinery/power/supermatter_crystal/S in supermatters) . = max(., S.get_status()) +/** + * Sets up the signal listener for Supermatter delaminations. + * + * Unregisters any old listners for SM delams, and then registers one for the SM refered + * to in the `active` variable. This proc is also used with no active SM to simply clear + * the signal and exit. + */ +/datum/computer_file/program/supermatter_monitor/proc/set_signals() + // if(active) + // RegisterSignal(active, COMSIG_SUPERMATTER_DELAM_ALARM, .proc/send_alert, override = TRUE) + // RegisterSignal(active, COMSIG_SUPERMATTER_DELAM_START_ALARM, .proc/send_start_alert, override = TRUE) + +/** + * Removes the signal listener for Supermatter delaminations from the selected supermatter. + * + * Pretty much does what it says. + */ +/datum/computer_file/program/supermatter_monitor/proc/clear_signals() + // if(active) + // UnregisterSignal(active, COMSIG_SUPERMATTER_DELAM_ALARM) + // UnregisterSignal(active, COMSIG_SUPERMATTER_DELAM_START_ALARM) + +/** + * Sends an SM delam alert to the computer. + * + * Triggered by a signal from the selected supermatter, this proc sends a notification + * to the computer if the program is either closed or minimized. We do not send these + * notifications to the comptuer if we're the active program, because engineers fixing + * the supermatter probably don't need constant beeping to distract them. + */ +/datum/computer_file/program/supermatter_monitor/proc/send_alert() + if(!computer.get_ntnet_status()) + return + if(computer.active_program != src) + computer.alert_call(src, "Crystal delamination in progress!") + alert_pending = TRUE + +/** + * Sends an SM delam start alert to the computer. + * + * Triggered by a signal from the selected supermatter at the start of a delamination, + * this proc sends a notification to the computer if this program is the active one. + * We do this so that people carrying a tablet with NT CIMS open but with the NTOS window + * closed will still get one audio alert. This is not sent to computers with the program + * minimized or closed to avoid double-notifications. + */ +/datum/computer_file/program/supermatter_monitor/proc/send_start_alert() + if(!computer.get_ntnet_status()) + return + if(computer.active_program == src) + computer.alert_call(src, "Crystal delamination in progress!") + /datum/computer_file/program/supermatter_monitor/ui_data() var/list/data = get_header_data() @@ -107,11 +166,13 @@ return data /datum/computer_file/program/supermatter_monitor/ui_act(action, params) - if(..()) - return TRUE + . = ..() + if(.) + return switch(action) if("PRG_clear") + clear_signals() active = null return TRUE if("PRG_refresh") @@ -122,4 +183,5 @@ for(var/obj/machinery/power/supermatter_crystal/S in supermatters) if(S.uid == newuid) active = S + set_signals() return TRUE diff --git a/code/modules/modular_computers/hardware/_hardware.dm b/code/modules/modular_computers/hardware/_hardware.dm index 81555340b2..0ccb9f6b96 100644 --- a/code/modules/modular_computers/hardware/_hardware.dm +++ b/code/modules/modular_computers/hardware/_hardware.dm @@ -24,8 +24,8 @@ /obj/item/computer_hardware/New(obj/L) ..() - pixel_x = rand(-8, 8) - pixel_y = rand(-8, 8) + pixel_x = initial(pixel_x) + rand(-8, 8) + pixel_y = initial(pixel_y) + rand(-8, 8) /obj/item/computer_hardware/Destroy() if(holder) @@ -94,12 +94,20 @@ // Called when component is removed from PC. /obj/item/computer_hardware/proc/on_remove(obj/item/modular_computer/M, mob/living/user = null) - try_eject(forced = 1) + try_eject(forced = TRUE) // Called when someone tries to insert something in it - paper in printer, card in card reader, etc. /obj/item/computer_hardware/proc/try_insert(obj/item/I, mob/living/user = null) return FALSE -// Called when someone tries to eject something from it - card from card reader, etc. -/obj/item/computer_hardware/proc/try_eject(slot=0, mob/living/user = null, forced = 0) +/** + * Implement this when your hardware contains an object that the user can eject. + * + * Examples include ejecting cells from battery modules, ejecting an ID card from a card reader + * or ejecting an Intellicard from an AI card slot. + * Arguments: + * * user - The mob requesting the eject. + * * forced - Whether this action should be forced in some way. + */ +/obj/item/computer_hardware/proc/try_eject(mob/living/user = null, forced = FALSE) return FALSE diff --git a/code/modules/modular_computers/hardware/ai_slot.dm b/code/modules/modular_computers/hardware/ai_slot.dm index c874d786a0..5d42747308 100644 --- a/code/modules/modular_computers/hardware/ai_slot.dm +++ b/code/modules/modular_computers/hardware/ai_slot.dm @@ -12,7 +12,7 @@ /obj/item/computer_hardware/ai_slot/handle_atom_del(atom/A) if(A == stored_card) - try_eject(0, null, TRUE) + try_eject(forced = TRUE) . = ..() /obj/item/computer_hardware/ai_slot/examine(mob/user) @@ -39,7 +39,7 @@ return TRUE -/obj/item/computer_hardware/ai_slot/try_eject(mob/living/user = null,forced = FALSE) +/obj/item/computer_hardware/ai_slot/try_eject(mob/living/user = null, forced = FALSE) if(!stored_card) to_chat(user, "There is no card in \the [src].") return FALSE @@ -65,5 +65,5 @@ return if(I.tool_behaviour == TOOL_SCREWDRIVER) to_chat(user, "You press down on the manual eject button with \the [I].") - try_eject(,user,1) + try_eject(user, TRUE) return diff --git a/code/modules/modular_computers/hardware/card_slot.dm b/code/modules/modular_computers/hardware/card_slot.dm index c243bf7db1..9139eee0b0 100644 --- a/code/modules/modular_computers/hardware/card_slot.dm +++ b/code/modules/modular_computers/hardware/card_slot.dm @@ -1,5 +1,5 @@ /obj/item/computer_hardware/card_slot - name = "identification card authentication module" // \improper breaks the find_hardware_by_name proc + name = "primary RFID card module" // \improper breaks the find_hardware_by_name proc desc = "A module allowing this computer to read or write data on ID cards. Necessary for some programs to run properly." power_usage = 10 //W icon_state = "card_mini" @@ -14,7 +14,7 @@ . = ..() /obj/item/computer_hardware/card_slot/Destroy() - try_eject() + try_eject(forced = TRUE) return ..() /obj/item/computer_hardware/card_slot/GetAccess() @@ -100,14 +100,16 @@ to_chat(user, "You adjust the connecter to fit into [expansion_hw ? "an expansion bay" : "the primary ID bay"].") /** - *Swaps the card_slot hardware between using the dedicated card slot bay on a computer, and using an expansion bay. + *Swaps the card_slot hardware between using the dedicated card slot bay on a computer, and using an expansion bay. */ /obj/item/computer_hardware/card_slot/proc/swap_slot() expansion_hw = !expansion_hw if(expansion_hw) device_type = MC_CARD2 + name = "secondary RFID card module" else device_type = MC_CARD + name = "primary RFID card module" /obj/item/computer_hardware/card_slot/examine(mob/user) . = ..() @@ -116,5 +118,6 @@ . += "There appears to be something loaded in the card slots." /obj/item/computer_hardware/card_slot/secondary + name = "secondary RFID card module" device_type = MC_CARD2 expansion_hw = TRUE diff --git a/code/modules/modular_computers/hardware/hard_drive.dm b/code/modules/modular_computers/hardware/hard_drive.dm index e5c133de20..8debb00c19 100644 --- a/code/modules/modular_computers/hardware/hard_drive.dm +++ b/code/modules/modular_computers/hardware/hard_drive.dm @@ -31,43 +31,43 @@ // Use this proc to add file to the drive. Returns 1 on success and 0 on failure. Contains necessary sanity checks. /obj/item/computer_hardware/hard_drive/proc/store_file(datum/computer_file/F) if(!F || !istype(F)) - return 0 + return FALSE if(!can_store_file(F)) - return 0 + return FALSE if(!check_functionality()) - return 0 + return FALSE if(!stored_files) - return 0 + return FALSE // This file is already stored. Don't store it again. if(F in stored_files) - return 0 + return FALSE F.holder = src stored_files.Add(F) recalculate_size() - return 1 + return TRUE // Use this proc to remove file from the drive. Returns 1 on success and 0 on failure. Contains necessary sanity checks. /obj/item/computer_hardware/hard_drive/proc/remove_file(datum/computer_file/F) if(!F || !istype(F)) - return 0 + return FALSE if(!stored_files) - return 0 + return FALSE if(!check_functionality()) - return 0 + return FALSE if(F in stored_files) stored_files -= F recalculate_size() - return 1 + return TRUE else - return 0 + return FALSE // Loops through all stored files and recalculates used_capacity of this drive /obj/item/computer_hardware/hard_drive/proc/recalculate_size() @@ -80,24 +80,24 @@ // Checks whether file can be stored on the hard drive. We can only store unique files, so this checks whether we wouldn't get a duplicity by adding a file. /obj/item/computer_hardware/hard_drive/proc/can_store_file(datum/computer_file/F) if(!F || !istype(F)) - return 0 + return FALSE if(F in stored_files) - return 0 + return FALSE var/name = F.filename + "." + F.filetype for(var/datum/computer_file/file in stored_files) if((file.filename + "." + file.filetype) == name) - return 0 + return FALSE // In the unlikely event someone manages to create that many files. // BYOND is acting weird with numbers above 999 in loops (infinite loop prevention) if(stored_files.len >= 999) - return 0 + return FALSE if((used_capacity + F.size) > max_capacity) - return 0 + return FALSE else - return 1 + return TRUE // Tries to find the file by filename. Returns null on failure @@ -157,7 +157,14 @@ max_capacity = 64 icon_state = "ssd_mini" w_class = WEIGHT_CLASS_TINY - custom_price = 150 + custom_price = PAYCHECK_MEDIUM * 2 + +// For borg integrated tablets. No downloader. +/obj/item/computer_hardware/hard_drive/small/integrated/install_default_programs() + store_file(new /datum/computer_file/program/computerconfig(src)) // Computer configuration utility, allows hardware control and displays more info than status bar + store_file(new /datum/computer_file/program/filemanager(src)) // File manager, allows text editor functions and basic file manipulation. + store_file(new /datum/computer_file/program/robotact(src)) + // Syndicate variant - very slight better /obj/item/computer_hardware/hard_drive/small/syndicate diff --git a/code/modules/modular_computers/hardware/network_card.dm b/code/modules/modular_computers/hardware/network_card.dm index 04bf494fe4..625ead6ed7 100644 --- a/code/modules/modular_computers/hardware/network_card.dm +++ b/code/modules/modular_computers/hardware/network_card.dm @@ -77,3 +77,23 @@ power_usage = 100 // Better range but higher power usage. icon_state = "net_wired" w_class = WEIGHT_CLASS_NORMAL + +/obj/item/computer_hardware/network_card/integrated //Borg tablet version, only works while the borg has power and is not locked + name = "cyborg data link" + +/obj/item/computer_hardware/network_card/integrated/get_signal(specific_action = 0) + var/obj/item/modular_computer/tablet/integrated/modularInterface = holder + + if(!modularInterface || !istype(modularInterface)) + return FALSE //wrong type of tablet + + if(!modularInterface.borgo) + return FALSE //No borg found + + if(modularInterface.borgo.locked_down) + return FALSE //lockdown restricts borg networking + + if(!modularInterface.borgo.cell || modularInterface.borgo.cell.charge == 0) + return FALSE //borg cell dying restricts borg networking + + return ..() diff --git a/code/modules/modular_computers/hardware/portable_disk.dm b/code/modules/modular_computers/hardware/portable_disk.dm index 89b0382e86..f1c565188f 100644 --- a/code/modules/modular_computers/hardware/portable_disk.dm +++ b/code/modules/modular_computers/hardware/portable_disk.dm @@ -4,7 +4,7 @@ power_usage = 10 icon_state = "datadisk6" w_class = WEIGHT_CLASS_TINY - critical = 0 + critical = FALSE max_capacity = 16 device_type = MC_SDD diff --git a/code/modules/modular_computers/hardware/recharger.dm b/code/modules/modular_computers/hardware/recharger.dm index 13ae6c1f39..ecfbf4c6b2 100644 --- a/code/modules/modular_computers/hardware/recharger.dm +++ b/code/modules/modular_computers/hardware/recharger.dm @@ -6,8 +6,8 @@ /obj/item/computer_hardware/recharger/proc/use_power(amount, charging=0) if(charging) - return 1 - return 0 + return TRUE + return FALSE /obj/item/computer_hardware/recharger/process() ..() @@ -23,28 +23,28 @@ holder.give_power(charge_rate * GLOB.CELLRATE) -/obj/item/computer_hardware/recharger/APC +/obj/item/computer_hardware/recharger/apc_recharger name = "area power connector" desc = "A device that wirelessly recharges connected device from nearby APC." icon_state = "charger_APC" w_class = WEIGHT_CLASS_SMALL // Can't be installed into tablets/PDAs -/obj/item/computer_hardware/recharger/APC/use_power(amount, charging=0) +/obj/item/computer_hardware/recharger/apc_recharger/use_power(amount, charging=0) if(ismachinery(holder.physical)) var/obj/machinery/M = holder.physical if(M.powered()) M.use_power(amount) - return 1 + return TRUE else var/area/A = get_area(src) if(!istype(A)) - return 0 + return FALSE if(A.powered(EQUIP)) A.use_power(amount, EQUIP) - return 1 - return 0 + return TRUE + return FALSE /obj/item/computer_hardware/recharger/wired name = "wired power connector" @@ -56,27 +56,34 @@ if(ismachinery(M.physical) && M.physical.anchored) return ..() to_chat(user, "\The [src] is incompatible with portable computers!") - return 0 + return FALSE /obj/item/computer_hardware/recharger/wired/use_power(amount, charging=0) if(ismachinery(holder.physical) && holder.physical.anchored) var/obj/machinery/M = holder.physical var/turf/T = M.loc if(!T || !istype(T)) - return 0 + return FALSE var/obj/structure/cable/C = T.get_cable_node() if(!C || !C.powernet) - return 0 + return FALSE var/power_in_net = C.powernet.avail-C.powernet.load if(power_in_net && power_in_net > amount) C.powernet.load += amount - return 1 + return TRUE + return FALSE - return 0 +/// This recharger exists only in borg built-in tablets. I would have tied it to the borg's cell but +/// the program that displays laws should always be usable, and the exceptions were starting to pile. +/obj/item/computer_hardware/recharger/cyborg + name = "modular interface power harness" + desc = "A standard connection to power a small computer device from a cyborg's chassis." +/obj/item/computer_hardware/recharger/cyborg/use_power(amount, charging=0) + return TRUE // This is not intended to be obtainable in-game. Intended for adminbus and debugging purposes. diff --git a/code/modules/modular_computers/laptop_vendor.dm b/code/modules/modular_computers/laptop_vendor.dm index 83bb057d66..0811983088 100644 --- a/code/modules/modular_computers/laptop_vendor.dm +++ b/code/modules/modular_computers/laptop_vendor.dm @@ -100,7 +100,7 @@ if(dev_apc_recharger) total_price += 399 if(fabricate) - fabricated_laptop.install_component(new /obj/item/computer_hardware/recharger/APC) + fabricated_laptop.install_component(new /obj/item/computer_hardware/recharger/apc_recharger) if(dev_printer) total_price += 99 if(fabricate) @@ -169,8 +169,9 @@ /obj/machinery/lapvend/ui_act(action, params) - if(..()) - return TRUE + . = ..() + if(.) + return switch(action) if("pick_device") diff --git a/code/modules/movespeed/_movespeed_modifier.dm b/code/modules/movespeed/_movespeed_modifier.dm index 9c8036bd55..46d10afe7f 100644 --- a/code/modules/movespeed/_movespeed_modifier.dm +++ b/code/modules/movespeed/_movespeed_modifier.dm @@ -96,7 +96,7 @@ GLOBAL_LIST_EMPTY(movespeed_modification_cache) return TRUE remove_movespeed_modifier(existing, FALSE) if(length(movespeed_modification)) - BINARY_INSERT(type_or_datum.id, movespeed_modification, datum/movespeed_modifier, type_or_datum, priority, COMPARE_VALUE) + BINARY_INSERT(type_or_datum.id, movespeed_modification, /datum/movespeed_modifier, type_or_datum, priority, COMPARE_VALUE) LAZYSET(movespeed_modification, type_or_datum.id, type_or_datum) if(update) update_movespeed() @@ -217,13 +217,25 @@ GLOBAL_LIST_EMPTY(movespeed_modification_cache) else continue . = M.apply_multiplicative(., src) - var/old = cached_multiplicative_slowdown // CITAEDL EDIT - To make things a bit less jarring, when in situations where // your delay decreases, "give" the delay back to the client cached_multiplicative_slowdown = . - var/diff = old - cached_multiplicative_slowdown - if((diff > 0) && client) + if(!client) + return + var/diff = (client.last_move - client.move_delay) - cached_multiplicative_slowdown + if(diff > 0) if(client.move_delay > world.time + 1.5) client.move_delay -= diff + var/timeleft = world.time - client.move_delay + var/elapsed = world.time - client.last_move + var/glide_size_current = glide_size + if((timeleft <= 0) || (elapsed > 20)) + set_glide_size(16, TRUE) + return + var/pixels_moved = glide_size_current * elapsed * (1 / world.tick_lag) + // calculate glidesize needed to move to the next tile within timeleft deciseconds + var/ticks_allowed = timeleft / world.tick_lag + var/pixels_per_tick = pixels_moved / ticks_allowed + set_glide_size(pixels_per_tick * GLOB.glide_size_multiplier, TRUE) /// Get the move speed modifiers list of the mob /mob/proc/get_movespeed_modifiers() diff --git a/code/modules/movespeed/modifiers/mobs.dm b/code/modules/movespeed/modifiers/mobs.dm index d453124b71..cdfb275418 100644 --- a/code/modules/movespeed/modifiers/mobs.dm +++ b/code/modules/movespeed/modifiers/mobs.dm @@ -62,6 +62,11 @@ var/mod = CONFIG_GET(number/movedelay/walk_delay) multiplicative_slowdown = isnum(mod)? mod : initial(multiplicative_slowdown) +/datum/movespeed_modifier/config_wak_run/walk/apply_multiplicative(existing, mob/target) + . = ..() + if(HAS_TRAIT(target, TRAIT_SPEEDY_STEP)) + . -= 1.25 + /datum/movespeed_modifier/config_walk_run/run/sync() var/mod = CONFIG_GET(number/movedelay/run_delay) multiplicative_slowdown = isnum(mod)? mod : initial(multiplicative_slowdown) diff --git a/code/modules/movespeed/modifiers/reagents.dm b/code/modules/movespeed/modifiers/reagents.dm index b6c2458670..1a03e8a602 100644 --- a/code/modules/movespeed/modifiers/reagents.dm +++ b/code/modules/movespeed/modifiers/reagents.dm @@ -12,3 +12,7 @@ /datum/movespeed_modifier/reagent/nitryl multiplicative_slowdown = -1 + +/datum/movespeed_modifier/reagent/meth + multiplicative_slowdown = -0.5 + absolute_max_tiles_per_second = 11 diff --git a/code/modules/newscaster/newscaster_machine.dm b/code/modules/newscaster/newscaster_machine.dm index 7e515bf0f5..c81a8f5d1a 100644 --- a/code/modules/newscaster/newscaster_machine.dm +++ b/code/modules/newscaster/newscaster_machine.dm @@ -552,7 +552,7 @@ GLOBAL_LIST_EMPTY(allCasters) updateUsrDialog() /obj/machinery/newscaster/attackby(obj/item/I, mob/living/user, params) - if(istype(I, /obj/item/wrench)) + if(I.tool_behaviour == TOOL_WRENCH) to_chat(user, "You start [anchored ? "un" : ""]securing [name]...") I.play_tool_sound(src) if(I.use_tool(src, user, 60)) @@ -566,7 +566,7 @@ GLOBAL_LIST_EMPTY(allCasters) to_chat(user, "You [anchored ? "un" : ""]secure [name].") new /obj/item/wallframe/newscaster(loc) qdel(src) - else if(istype(I, /obj/item/weldingtool) && user.a_intent != INTENT_HARM) + else if(I.tool_behaviour == TOOL_WELDER && user.a_intent != INTENT_HARM) if(stat & BROKEN) if(!I.tool_start_check(user, amount=0)) return diff --git a/code/modules/paperwork/carbonpaper.dm b/code/modules/paperwork/carbonpaper.dm index dc8f172069..9a306333b3 100644 --- a/code/modules/paperwork/carbonpaper.dm +++ b/code/modules/paperwork/carbonpaper.dm @@ -8,14 +8,18 @@ var/iscopy = FALSE /obj/item/paper/carbon/update_icon_state() - if(iscopy) - icon_state = "cpaper" - else if(copied) - icon_state = "paper" - else - icon_state = "paper_stack" if(info) icon_state = "[icon_state]_words" + return ..() + if(iscopy) + icon_state = "cpaper" + return ..() + if(copied) + icon_state = "paper" + return ..() + + icon_state = "paper_stack" + return ..() /obj/item/paper/carbon/proc/removecopy(mob/living/user) if(!copied) diff --git a/code/modules/paperwork/clipboard.dm b/code/modules/paperwork/clipboard.dm index 5b576a2438..13de7898c1 100644 --- a/code/modules/paperwork/clipboard.dm +++ b/code/modules/paperwork/clipboard.dm @@ -9,8 +9,8 @@ w_class = WEIGHT_CLASS_SMALL throw_speed = 3 throw_range = 7 - var/obj/item/pen/haspen //The stored pen. - var/obj/item/paper/toppaper //The topmost piece of paper. + var/obj/item/pen/haspen //The stored pen. + var/obj/item/paper/toppaper //The topmost piece of paper. slot_flags = ITEM_SLOT_BELT resistance_flags = FLAMMABLE @@ -24,7 +24,7 @@ /obj/item/clipboard/Destroy() QDEL_NULL(haspen) - QDEL_NULL(toppaper) //let movable/Destroy handle the rest + QDEL_NULL(toppaper) //let movable/Destroy handle the rest return ..() /obj/item/clipboard/update_overlays() @@ -55,7 +55,7 @@ else dat += "Add Pen

    " - //The topmost paper. You can't organise contents directly in byond, so this is what we're stuck with. -Pete + //The topmost paper. You can't organise contents directly in byond, so this is what we're stuck with. -Pete if(toppaper) var/obj/item/paper/P = toppaper dat += "Write Remove - [P.name]

    " @@ -71,7 +71,7 @@ /obj/item/clipboard/Topic(href, href_list) ..() - if(usr.stat || usr.restrained()) + if(usr.stat != CONSCIOUS || usr.restrained()) //HAS_TRAIT(usr, TRAIT_HANDS_BLOCKED)) return if(usr.contents.Find(src)) diff --git a/code/modules/paperwork/filingcabinet.dm b/code/modules/paperwork/filingcabinet.dm index 390cd0cf83..59bbf47128 100644 --- a/code/modules/paperwork/filingcabinet.dm +++ b/code/modules/paperwork/filingcabinet.dm @@ -1,9 +1,9 @@ /* Filing cabinets! * Contains: - * Filing Cabinets - * Security Record Cabinets - * Medical Record Cabinets - * Employment Contract Cabinets + * Filing Cabinets + * Security Record Cabinets + * Medical Record Cabinets + * Employment Contract Cabinets */ @@ -27,7 +27,7 @@ desc = "A small cabinet with drawers. This one has wheels!" anchored = FALSE -/obj/structure/filingcabinet/filingcabinet //not changing the path to avoid unnecessary map issues, but please don't name stuff like this in the future -Pete +/obj/structure/filingcabinet/filingcabinet //not changing the path to avoid unnecessary map issues, but please don't name stuff like this in the future -Pete icon_state = "tallcabinet" @@ -45,12 +45,12 @@ I.forceMove(loc) qdel(src) -/obj/structure/filingcabinet/attackby(obj/item/P, mob/user, params) +/obj/structure/filingcabinet/attackby(obj/item/P, mob/living/user, params) if(P.tool_behaviour == TOOL_WRENCH && user.a_intent != INTENT_HELP) to_chat(user, "You begin to [anchored ? "unwrench" : "wrench"] [src].") if(P.use_tool(src, user, 20, volume=50)) to_chat(user, "You successfully [anchored ? "unwrench" : "wrench"] [src].") - anchored = !anchored + set_anchored(!anchored) else if(P.w_class < WEIGHT_CLASS_NORMAL) if(!user.transferItemToLoc(P, src)) return @@ -79,13 +79,15 @@ dat += "
    " user << browse("[name][dat]", "window=filingcabinet;size=350x300") + /obj/structure/filingcabinet/attack_tk(mob/user) if(anchored) - attack_self_tk(user) - else - ..() + return attack_self_tk(user) + return ..() + /obj/structure/filingcabinet/attack_self_tk(mob/user) + // . = COMPONENT_CANCEL_ATTACK_CHAIN if(contents.len) if(prob(40 + contents.len * 5)) var/obj/item/I = pick(contents) @@ -96,8 +98,9 @@ return to_chat(user, "You find nothing in [src].") + /obj/structure/filingcabinet/Topic(href, href_list) - if(!usr.canUseTopic(src, BE_CLOSE, ismonkey(usr))) + if(!usr.canUseTopic(src, BE_CLOSE, ismonkey(usr), FALSE)) //, !iscyborg(usr))) return if(href_list["retrieve"]) usr << browse("", "window=filingcabinet") // Close the menu @@ -114,7 +117,7 @@ * Security Record Cabinets */ /obj/structure/filingcabinet/security - var/virgin = 1 + var/virgin = TRUE /obj/structure/filingcabinet/security/proc/populate() if(virgin) @@ -132,22 +135,23 @@ counter++ P.info += "" P.name = "paper - '[G.fields["name"]]'" - virgin = 0 //tabbing here is correct- it's possible for people to try and use it + virgin = FALSE //tabbing here is correct- it's possible for people to try and use it //before the records have been generated, so we do this inside the loop. -/obj/structure/filingcabinet/security/on_attack_hand() +/obj/structure/filingcabinet/security/on_attack_hand(mob/user, list/modifiers) populate() - . = ..() + return ..() /obj/structure/filingcabinet/security/attack_tk() populate() - ..() + return ..() /* * Medical Record Cabinets */ /obj/structure/filingcabinet/medical - var/virgin = 1 + ///This var is so that its filled on crew interaction to be as accurate (including latejoins) as possible, true until first interact + var/virgin = TRUE /obj/structure/filingcabinet/medical/proc/populate() if(virgin) @@ -165,17 +169,17 @@ counter++ P.info += "" P.name = "paper - '[G.fields["name"]]'" - virgin = 0 //tabbing here is correct- it's possible for people to try and use it + virgin = FALSE //tabbing here is correct- it's possible for people to try and use it //before the records have been generated, so we do this inside the loop. //ATTACK HAND IGNORING PARENT RETURN VALUE -/obj/structure/filingcabinet/medical/on_attack_hand() +/obj/structure/filingcabinet/medical/on_attack_hand(mob/user, list/modifiers) populate() - . = ..() + return ..() /obj/structure/filingcabinet/medical/attack_tk() populate() - ..() + return ..() /* * Employment contract Cabinets @@ -185,6 +189,7 @@ GLOBAL_LIST_EMPTY(employmentCabinets) /obj/structure/filingcabinet/employment icon_state = "employmentcabinet" + ///This var is so that its filled on crew interaction to be as accurate (including latejoins) as possible, true until first interact var/virgin = TRUE /obj/structure/filingcabinet/employment/Initialize() @@ -219,3 +224,4 @@ GLOBAL_LIST_EMPTY(employmentCabinets) fillCurrent() virgin = FALSE return ..() + diff --git a/code/modules/paperwork/folders.dm b/code/modules/paperwork/folders.dm index 305099d115..2c534d0178 100644 --- a/code/modules/paperwork/folders.dm +++ b/code/modules/paperwork/folders.dm @@ -76,7 +76,7 @@ /obj/item/folder/Topic(href, href_list) ..() - if(usr.stat || usr.restrained()) + if(usr.stat != CONSCIOUS || usr.restrained()) //HAS_TRAIT(usr, TRAIT_HANDS_BLOCKED)) return if(usr.contents.Find(src)) diff --git a/code/modules/paperwork/handlabeler.dm b/code/modules/paperwork/handlabeler.dm index da9fdc4ca4..ad251a6c82 100644 --- a/code/modules/paperwork/handlabeler.dm +++ b/code/modules/paperwork/handlabeler.dm @@ -40,7 +40,7 @@ . = ..() if(!proximity) return - if(!mode) //if it's off, give up. + if(!mode) //if it's off, give up. return if(!labels_left) @@ -57,7 +57,7 @@ return user.visible_message("[user] labels [A] with \"[label]\".", \ - "You label [A] with \"[label]\".") + "You label [A] with \"[label]\".") A.AddComponent(/datum/component/label, label) // playsound(A, 'sound/items/handling/component_pickup.ogg', 20, TRUE) labels_left-- @@ -86,7 +86,7 @@ if(istype(I, /obj/item/hand_labeler_refill)) to_chat(user, "You insert [I] into [src].") qdel(I) - labels_left = initial(labels_left) //Yes, it's capped at its initial value + labels_left = initial(labels_left) //Yes, it's capped at its initial value /obj/item/hand_labeler/borg name = "cyborg-hand labeler" diff --git a/code/modules/paperwork/paper.dm b/code/modules/paperwork/paper.dm index 20ec678e45..7a98287809 100644 --- a/code/modules/paperwork/paper.dm +++ b/code/modules/paperwork/paper.dm @@ -11,7 +11,6 @@ #define MODE_WRITING 1 #define MODE_STAMPING 2 - /** * Paper is now using markdown (like in github pull notes) for ALL rendering * so we do loose a bit of functionality but we gain in easy of use of @@ -23,9 +22,6 @@ icon = 'icons/obj/bureaucracy.dmi' icon_state = "paper" item_state = "paper" - // inhand_icon_state = "paper" - // worn_icon_state = "paper" - // custom_fire_overlay = "paper_onfire_overlay" throwforce = 0 w_class = WEIGHT_CLASS_TINY throw_range = 1 @@ -102,8 +98,8 @@ /obj/item/paper/Initialize() . = ..() - pixel_y = rand(-8, 8) - pixel_x = rand(-9, 9) + pixel_x = initial(pixel_x) + rand(-9, 9) + pixel_y = initial(pixel_y) + rand(-8, 8) update_icon() /obj/item/paper/update_icon_state() @@ -246,6 +242,8 @@ /obj/item/paper/ui_data(mob/user) var/list/data = list() + data["edit_usr"] = "[user]" + var/obj/O = user.get_active_held_item() if(istype(O, /obj/item/toy/crayon)) var/obj/item/toy/crayon/PEN = O @@ -284,7 +282,8 @@ return data /obj/item/paper/ui_act(action, params,datum/tgui/ui) - if(..()) + . = ..() + if(.) return switch(action) if("stamp") @@ -310,7 +309,8 @@ LAZYADD(stamped, stamp_icon_state) update_static_data(usr,ui) - ui.user.visible_message("[ui.user] stamps [src] with [stamp_class]!", "You stamp [src] with [stamp_class]!") + var/obj/O = ui.user.get_active_held_item() + ui.user.visible_message("[ui.user] stamps [src] with \the [O.name]!", "You stamp [src] with \the [O.name]!") else to_chat(usr, pick("You try to stamp but you miss!", "There is no where else you can stamp!")) . = TRUE diff --git a/code/modules/paperwork/paper_cutter.dm b/code/modules/paperwork/paper_cutter.dm index a3b9b23141..f5cb6e1a10 100644 --- a/code/modules/paperwork/paper_cutter.dm +++ b/code/modules/paperwork/paper_cutter.dm @@ -36,9 +36,10 @@ /obj/item/papercutter/update_icon_state() icon_state = (storedcutter ? "[initial(icon_state)]-cutter" : "[initial(icon_state)]") + return ..() /obj/item/papercutter/update_overlays() - . = ..() + . =..() if(storedpaper) . += "paper" @@ -68,6 +69,9 @@ ..() /obj/item/papercutter/on_attack_hand(mob/user) + // . = ..() + // if(.) + // return add_fingerprint(user) if(!storedcutter) to_chat(user, "The cutting blade is gone! You can't use [src] now.") @@ -118,8 +122,8 @@ /obj/item/paperslip/Initialize() . = ..() - pixel_x = rand(-5, 5) - pixel_y = rand(-5, 5) + pixel_x = initial(pixel_x) + rand(-5, 5) + pixel_y = initial(pixel_y) + rand(-5, 5) /obj/item/hatchet/cutterblade diff --git a/code/modules/paperwork/paper_premade.dm b/code/modules/paperwork/paper_premade.dm index 5d6d3d45e8..cc7b11d331 100644 --- a/code/modules/paperwork/paper_premade.dm +++ b/code/modules/paperwork/paper_premade.dm @@ -1,5 +1,5 @@ /* - * Premade paper + * Premade paper */ /obj/item/paper/fluff/sop @@ -21,12 +21,16 @@
    -Love, Your Dearest"} -//////////// Job guides n' fluff +//////////// Job guides n' fluff /obj/item/paper/guides/jobs/hydroponics name = "paper- 'Greetings from Billy Bob'" info = "Hey fellow botanist!
    \n
    \nI didn't trust the station folk so I left
    \na couple of weeks ago. But here's some
    \ninstructions on how to operate things here.
    \nYou can grow plants and each iteration they become
    \nstronger, more potent and have better yield, if you
    \nknow which ones to pick. Use your botanist's analyzer
    \nfor that. You can turn harvested plants into seeds
    \nat the seed extractor, and replant them for better stuff!
    \nSometimes if the weed level gets high in the tray
    \nmutations into different mushroom or weed species have
    \nbeen witnessed. On the rare occasion even weeds mutate!
    \n
    \nEither way, have fun!
    \n
    \nBest regards,
    \nBilly Bob Johnson.
    \n
    \nPS.
    \nHere's a few tips:
    \nIn nettles, potency = damage
    \nIn amanitas, potency = deadliness + side effect
    \nIn Liberty caps, potency = drug power + effect
    \nIn chilies, potency = heat
    \nNutrients keep mushrooms alive!
    \nWater keeps weeds such as nettles alive!
    \nAll other plants need both." +/obj/item/paper/guides/jobs/holopad_hydro + name = "paper- 'Holopad Notice'" + info = "Can't get any botanists at the table? Have you tried using the damn holopad?
    \n
    \nStep onto the pad, and interface with it
    \nthen make your dang ol' call!
    \n
    \nYou want to call \"Hydroponics\" to reach them." + /obj/item/paper/fluff/jobs/security/beepsky_mom name = "Note from Beepsky's Mom" info = "01001001 00100000 01101000 01101111 01110000 01100101 00100000 01111001 01101111 01110101 00100000 01110011 01110100 01100001 01111001 00100000 01110011 01100001 01100110 01100101 00101110 00100000 01001100 01101111 01110110 01100101 00101100 00100000 01101101 01101111 01101101 00101110" @@ -92,9 +96,11 @@ "} /* - * Stations + * Stations */ -////////// cogstation. + + +/////////// Cogstation. /obj/item/paper/guides/cogstation/job_changes name = "MEMO: Job Changes" @@ -220,7 +226,8 @@ name = "MEMO: MULEbots" info = "As you may know, MULEbots have been coded to minimize travel distance for maximum efficiency. In the case of this station, that may include travelling through depressurized areas exposed to space. Please bear this in mind before using them to transport living tissue.
    \n
    \nGenerated by Organic Resources Bot #2053" -/////////// CentCom + +/////////// CentCom /obj/item/paper/fluff/stations/centcom/disk_memo name = "memo" @@ -234,7 +241,7 @@ info = "
    CentCom Security
    Port Division
    Official Bulletin

    Inspector,
    There is an emergency shuttle arriving today.

    Approval is restricted to Nanotrasen employees only. Deny all other entrants.

    CentCom Port Commissioner" -/////////// Lavaland +/////////// Lavaland /obj/item/paper/fluff/stations/lavaland/orm_notice name = "URGENT!" diff --git a/code/modules/paperwork/paperbin.dm b/code/modules/paperwork/paperbin.dm index b8bbd0a30e..16a9ed33ed 100644 --- a/code/modules/paperwork/paperbin.dm +++ b/code/modules/paperwork/paperbin.dm @@ -132,6 +132,7 @@ icon_state = "paper_bin0" else icon_state = "[initial(icon_state)]" + return ..() /obj/item/paper_bin/update_overlays() . = ..() @@ -154,6 +155,7 @@ /obj/item/paper_bin/bundlenatural/on_attack_hand(mob/user) if(total_paper < 1) qdel(src) + return ..() /obj/item/paper_bin/bundlenatural/fire_act(exposed_temperature, exposed_volume) qdel(src) diff --git a/code/modules/paperwork/paperplane.dm b/code/modules/paperwork/paperplane.dm index 53fe886d65..d369339095 100644 --- a/code/modules/paperwork/paperplane.dm +++ b/code/modules/paperwork/paperplane.dm @@ -21,8 +21,8 @@ /obj/item/paperplane/Initialize(mapload, obj/item/paper/newPaper) . = ..() - pixel_y = rand(-8, 8) - pixel_x = rand(-9, 9) + pixel_x = initial(pixel_x) + rand(-9, 9) + pixel_y = initial(pixel_y) + rand(-8, 8) if(newPaper) internalPaper = newPaper flags_1 = newPaper.flags_1 @@ -65,17 +65,18 @@ /obj/item/paperplane/update_overlays() . = ..() var/list/stamped = internalPaper.stamped - if(stamped) - for(var/S in stamped) - . += "paperplane_[S]" + if(!LAZYLEN(stamped)) + return + for(var/S in stamped) + . += "paperplane_[S]" /obj/item/paperplane/attack_self(mob/user) to_chat(user, "You unfold [src].") - var/obj/item/paper/internal_paper_tmp = internalPaper - internal_paper_tmp.forceMove(loc) - internalPaper = null - qdel(src) - user.put_in_hands(internal_paper_tmp) + // We don't have to qdel the paperplane here; it shall be done once the internal paper object is moved out of src anyway. + if(user.Adjacent(internalPaper)) + user.put_in_hands(internalPaper) + else + internalPaper.forceMove(loc) /obj/item/paperplane/attackby(obj/item/P, mob/living/carbon/human/user, params) if(burn_paper_product_attackby_check(P, user)) @@ -84,7 +85,7 @@ to_chat(user, "You should unfold [src] before changing it!") return - else if(istype(P, /obj/item/stamp)) //we don't randomize stamps on a paperplane + else if(istype(P, /obj/item/stamp)) //we don't randomize stamps on a paperplane internalPaper.attackby(P, user) //spoofed attack to update internal paper. update_icon() add_fingerprint(user) @@ -121,8 +122,8 @@ . = ..() . += "Alt-click [src] to fold it into a paper plane." -/obj/item/paper/AltClick(mob/living/carbon/user, obj/item/I) - if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user))) +/obj/item/paper/AltClick(mob/living/user, obj/item/I) + if(!user.canUseTopic(src, BE_CLOSE, ismonkey(user), FALSE)) //, TRUE)) return if(istype(src, /obj/item/paper/carbon)) var/obj/item/paper/carbon/Carbon = src @@ -137,5 +138,6 @@ if(origami_action?.active) plane_type = /obj/item/paperplane/origami - I = new plane_type(user, src) - user.put_in_hands(I) + I = new plane_type(loc, src) + if(user.Adjacent(I)) + user.put_in_hands(I) diff --git a/code/modules/paperwork/pen.dm b/code/modules/paperwork/pen.dm index 8863a098fc..b4f1acbed4 100644 --- a/code/modules/paperwork/pen.dm +++ b/code/modules/paperwork/pen.dm @@ -1,9 +1,9 @@ -/* Pens! - * Contains: - * Pens - * Sleepy Pens - * Parapens - * Edaggers +/* Pens! + * Contains: + * Pens + * Sleepy Pens + * Parapens + * Edaggers */ @@ -26,7 +26,7 @@ custom_materials = list(/datum/material/iron=10) pressure_resistance = 2 grind_results = list(/datum/reagent/iron = 2, /datum/reagent/iodine = 1) - var/colour = "black" //what colour the ink is! + var/colour = "black" //what colour the ink is! var/degrees = 0 var/font = PEN_FONT embedding = list() @@ -147,29 +147,50 @@ /obj/item/pen/afterattack(obj/O, mob/living/user, proximity) . = ..() - //Changing Name/Description of items. Only works if they have the 'unique_rename' flag set + //Changing name/description of items. Only works if they have the UNIQUE_RENAME object flag set if(isobj(O) && proximity && (O.obj_flags & UNIQUE_RENAME)) - var/penchoice = input(user, "What would you like to edit?", "Rename or change description?") as null|anything in list("Rename","Change description") + var/penchoice = input(user, "What would you like to edit?", "Rename, change description or reset both?") as null|anything in list("Rename","Change description","Reset") if(QDELETED(O) || !user.canUseTopic(O, BE_CLOSE)) return if(penchoice == "Rename") - var/input = stripped_input(user,"What do you want to name \the [O.name]?", ,"", MAX_NAME_LEN) + var/input = stripped_input(user,"What do you want to name [O]?", ,"[O.name]", MAX_NAME_LEN) var/oldname = O.name if(QDELETED(O) || !user.canUseTopic(O, BE_CLOSE)) return - if(oldname == input) - to_chat(user, "You changed \the [O.name] to... well... \the [O.name].") + if(oldname == input || input == "") + to_chat(user, "You changed [O] to... well... [O].") else O.name = input - to_chat(user, "\The [oldname] has been successfully been renamed to \the [input].") + var/datum/component/label/label = O.GetComponent(/datum/component/label) + if(label) + label.remove_label() + label.apply_label() + to_chat(user, "You have successfully renamed \the [oldname] to [O].") O.renamedByPlayer = TRUE if(penchoice == "Change description") - var/input = stripped_input(user,"Describe \the [O.name] here", ,"", 100) + var/input = stripped_input(user,"Describe [O] here:", ,"[O.desc]", 140) + var/olddesc = O.desc if(QDELETED(O) || !user.canUseTopic(O, BE_CLOSE)) return - O.desc = input - to_chat(user, "You have successfully changed \the [O.name]'s description.") + if(olddesc == input || input == "") + to_chat(user, "You decide against changing [O]'s description.") + else + O.desc = input + to_chat(user, "You have successfully changed [O]'s description.") + O.renamedByPlayer = TRUE + + if(penchoice == "Reset") + if(QDELETED(O) || !user.canUseTopic(O, BE_CLOSE)) + return + O.desc = initial(O.desc) + O.name = initial(O.name) + var/datum/component/label/label = O.GetComponent(/datum/component/label) + if(label) + label.remove_label() + label.apply_label() + to_chat(user, "You have successfully reset [O]'s name and description.") + O.renamedByPlayer = FALSE /* * Sleepypens @@ -256,6 +277,7 @@ item_state = initial(item_state) lefthand_file = initial(lefthand_file) righthand_file = initial(righthand_file) + return ..() /obj/item/pen/survival name = "survival pen" diff --git a/code/modules/paperwork/photocopier.dm b/code/modules/paperwork/photocopier.dm index 25a9cf0043..06adda95dd 100644 --- a/code/modules/paperwork/photocopier.dm +++ b/code/modules/paperwork/photocopier.dm @@ -1,19 +1,19 @@ /// For use with the `color_mode` var. Photos will be printed in greyscale while the var has this value. -#define PHOTO_GREYSCALE "Greyscale" +#define PHOTO_GREYSCALE "Greyscale" /// For use with the `color_mode` var. Photos will be printed in full color while the var has this value. -#define PHOTO_COLOR "Color" +#define PHOTO_COLOR "Color" /// How much toner is used for making a copy of a paper. -#define PAPER_TONER_USE 0.125 +#define PAPER_TONER_USE 0.125 /// How much toner is used for making a copy of a photo. -#define PHOTO_TONER_USE 0.625 +#define PHOTO_TONER_USE 0.625 /// How much toner is used for making a copy of a document. -#define DOCUMENT_TONER_USE 0.75 +#define DOCUMENT_TONER_USE 0.75 /// How much toner is used for making a copy of an ass. -#define ASS_TONER_USE 0.625 +#define ASS_TONER_USE 0.625 /// The maximum amount of copies you can make with one press of the copy button. -#define MAX_COPIES_AT_ONCE 10 +#define MAX_COPIES_AT_ONCE 10 /obj/machinery/photocopier name = "photocopier" @@ -46,7 +46,7 @@ /obj/machinery/photocopier/Initialize() . = ..() - //AddComponent(/datum/component/payment, 5, SSeconomy.get_dep_account(ACCOUNT_CIV), PAYMENT_CLINICAL) + // AddComponent(/datum/component/payment, 5, SSeconomy.get_dep_account(ACCOUNT_CIV), PAYMENT_CLINICAL) toner_cartridge = new(src) /obj/machinery/photocopier/ui_interact(mob/user, datum/tgui/ui) @@ -82,7 +82,8 @@ return data /obj/machinery/photocopier/ui_act(action, list/params) - if(..()) + . = ..() + if(.) return switch(action) @@ -187,13 +188,12 @@ */ /obj/machinery/photocopier/proc/do_copy_loop(datum/callback/copy_cb, mob/user) busy = TRUE - var/num_loops - for(var/i in 1 to num_copies) - //if(attempt_charge(src, user) & COMPONENT_OBJ_CANCEL_CHARGE) - // break + var/i + for(i in 1 to num_copies) + // if(attempt_charge(src, user) & COMPONENT_OBJ_CANCEL_CHARGE) + // break addtimer(copy_cb, i SECONDS) - num_loops++ - addtimer(CALLBACK(src, .proc/reset_busy), num_loops SECONDS) + addtimer(CALLBACK(src, .proc/reset_busy), i SECONDS) /** * Sets busy to `FALSE`. Created as a proc so it can be used in callbacks. @@ -210,8 +210,8 @@ * * copied_item - The paper, document, or photo that was just spawned on top of the printer. */ /obj/machinery/photocopier/proc/give_pixel_offset(obj/item/copied_item) - copied_item.pixel_x = rand(-10, 10) - copied_item.pixel_y = rand(-10, 10) + copied_item.pixel_x = initial(copied_item.pixel_x) + rand(-10, 10) + copied_item.pixel_y = initial(copied_item.pixel_y) + rand(-10, 10) /** * Handles the copying of devil contract paper. Transfers all the text, stamps and so on from the old paper, to the copy. @@ -242,8 +242,8 @@ copied_paper.info = "" var/copied_info = paper_copy.info - copied_info = replacetext(copied_info, "" copied_paper.name = paper_copy.name @@ -287,17 +287,22 @@ /obj/machinery/photocopier/proc/make_ass_copy() if(!check_ass()) return - if(ishuman(ass)) //(ass.get_item_by_slot(ITEM_SLOT_ICLOTHING) || ass.get_item_by_slot(ITEM_SLOT_OCLOTHING))) - var/mob/living/carbon/C = ass //have to typecast to this, is_groin_exposed is carbon level - if(C.is_groin_exposed()) - to_chat(usr, "You feel kind of silly, copying [ass == usr ? "your" : ass][ass == usr ? "" : "\'s"] ass with [ass == usr ? "your" : "[ass.p_their()]"] clothes on." ) - return + if(ishuman(ass) && (ass.get_item_by_slot(ITEM_SLOT_ICLOTHING) || ass.get_item_by_slot(ITEM_SLOT_OCLOTHING))) + to_chat(usr, "You feel kind of silly, copying [ass == usr ? "your" : ass][ass == usr ? "" : "\'s"] ass with [ass == usr ? "your" : "[ass.p_their()]"] clothes on." ) + return var/icon/temp_img - if(isalienadult(ass) || istype(ass, /mob/living/simple_animal/hostile/alien)) //Xenos have their own asses, thanks to Pybro. + if(ishuman(ass)) + var/mob/living/carbon/human/H = ass + var/datum/species/spec = H.dna.species + if(spec.ass_image) + temp_img = icon(spec.ass_image) + else + temp_img = icon(ass.gender == FEMALE ? 'icons/ass/assfemale.png' : 'icons/ass/assmale.png') + else if(isalienadult(ass) || istype(ass, /mob/living/simple_animal/hostile/alien)) //Xenos have their own asses, thanks to Pybro. temp_img = icon('icons/ass/assalien.png') - else if(ishuman(ass)) //Suit checks are after check_ass - temp_img = icon(ass.gender == FEMALE ? 'icons/ass/assfemale.png' : 'icons/ass/assmale.png') + else if(issilicon(ass)) + temp_img = icon('icons/ass/assmachine.png') else if(isdrone(ass)) //Drones are hot temp_img = icon('icons/ass/assdrone.png') @@ -479,6 +484,7 @@ */ /obj/item/toner name = "toner cartridge" + desc = "A small, lightweight cartridge of NanoTrasen ValueBrand toner. Fits photocopiers and autopainters alike." icon = 'icons/obj/device.dmi' icon_state = "tonercartridge" grind_results = list(/datum/reagent/iodine = 40, /datum/reagent/iron = 10) @@ -487,9 +493,10 @@ /obj/item/toner/large name = "large toner cartridge" + desc = "A hefty cartridge of NanoTrasen ValueBrand toner. Fits photocopiers and autopainters alike." grind_results = list(/datum/reagent/iodine = 90, /datum/reagent/iron = 10) - charges = 15 - max_charges = 15 + charges = 25 + max_charges = 25 /obj/item/toner/extreme name = "extremely large toner cartridge" diff --git a/code/modules/paperwork/ticketmachine.dm b/code/modules/paperwork/ticketmachine.dm index 25b5cfb51b..e874f2836d 100644 --- a/code/modules/paperwork/ticketmachine.dm +++ b/code/modules/paperwork/ticketmachine.dm @@ -5,6 +5,7 @@ name = "ticket machine" icon = 'icons/obj/bureaucracy.dmi' icon_state = "ticketmachine" + // base_icon_state = "ticketmachine" desc = "A marvel of bureaucratic engineering encased in an efficient plastic shell. It can be refilled with a hand labeler refill roll and linked to buttons with a multitool." density = FALSE maptext_height = 26 @@ -117,6 +118,10 @@ addtimer(VARSET_CALLBACK(src, cooldown, FALSE), 10) /obj/machinery/ticket_machine/update_icon() + . = ..() + handle_maptext() + +/obj/machinery/ticket_machine/update_icon_state() switch(ticket_number) //Gives you an idea of how many tickets are left if(0 to 49) icon_state = "ticketmachine_100" @@ -124,7 +129,7 @@ icon_state = "ticketmachine_50" if(100) icon_state = "ticketmachine_0" - handle_maptext() + return ..() /obj/machinery/ticket_machine/proc/handle_maptext() switch(ticket_number) //This is here to handle maptext offsets so that the numbers align. @@ -161,9 +166,7 @@ ready = TRUE /obj/machinery/ticket_machine/on_attack_hand(mob/living/carbon/user) - INVOKE_ASYNC(src, .proc/attempt_ticket, user) - -/obj/machinery/ticket_machine/proc/attempt_ticket(mob/living/carbon/user) + // . = ..() if(!ready) to_chat(user,"You press the button, but nothing happens...") return diff --git a/code/modules/photography/photos/frame.dm b/code/modules/photography/photos/frame.dm index d306c46815..4000bf843c 100644 --- a/code/modules/photography/photos/frame.dm +++ b/code/modules/photography/photos/frame.dm @@ -115,14 +115,14 @@ return ..() /obj/structure/sign/picture_frame/attackby(obj/item/I, mob/user, params) - if(can_decon && (istype(I, /obj/item/screwdriver) || istype(I, /obj/item/wrench))) + if(can_decon && (I.tool_behaviour == TOOL_SCREWDRIVER || I.tool_behaviour == TOOL_WRENCH)) to_chat(user, "You start unsecuring [name]...") if(I.use_tool(src, user, 30, volume=50)) playsound(loc, 'sound/items/deconstruct.ogg', 50, 1) to_chat(user, "You unsecure [name].") deconstruct() - else if(istype(I, /obj/item/wirecutters) && framed) + else if(I.tool_behaviour == TOOL_WIRECUTTER && framed) framed.forceMove(drop_location()) framed = null user.visible_message("[user] cuts away [framed] from [src]!") diff --git a/code/modules/pool/pool_controller.dm b/code/modules/pool/pool_controller.dm index 17faa7a3e3..9f670de282 100644 --- a/code/modules/pool/pool_controller.dm +++ b/code/modules/pool/pool_controller.dm @@ -146,7 +146,7 @@ return reagents.clear_reagents() // This also reacts them. No nitroglycerin deathpools, sorry gamers :( - W.reagents.trans_to(reagents, max_beaker_transfer) + W.reagents.trans_to(reagents, max_beaker_transfer, log = "pool fill from reagent container") user.visible_message("[src] makes a slurping noise.", "All of the contents of [W] are quickly suctioned out by the machine!You cut the cable.") @@ -177,7 +177,7 @@ By design, d1 is the smallest direction and d2 is the highest R.loaded.cable_join(src, user) R.is_empty(user) - else if(istype(W, /obj/item/multitool)) + else if(W.tool_behaviour == TOOL_MULTITOOL) if(powernet && (powernet.avail > 0)) // is it powered? to_chat(user, "[DisplayPower(powernet.avail)] in power network.") else @@ -531,7 +531,7 @@ By design, d1 is the smallest direction and d2 is the highest user.visible_message("[user] is strangling [user.p_them()]self with [src]! It looks like [user.p_theyre()] trying to commit suicide!") return(OXYLOSS) -/obj/item/stack/cable_coil/Initialize(mapload, new_amount = null) +/obj/item/stack/cable_coil/Initialize(mapload, new_amount, merge = TRUE) . = ..() pixel_x = rand(-2,2) pixel_y = rand(-2,2) @@ -823,7 +823,7 @@ By design, d1 is the smallest direction and d2 is the highest /obj/item/stack/cable_coil/random color = "#ffffff" -/obj/item/stack/cable_coil/random/Initialize(mapload, new_amount = null, param_color = null) +/obj/item/stack/cable_coil/random/Initialize(mapload, new_amount, merge = TRUE, param_color = null) . = ..() var/list/cable_colors = GLOB.cable_colors color = pick(cable_colors) @@ -835,12 +835,13 @@ By design, d1 is the smallest direction and d2 is the highest amount = null icon_state = "coil2" -/obj/item/stack/cable_coil/cut/Initialize(mapload) - . = ..() +/obj/item/stack/cable_coil/cut/Initialize(mapload, new_amount, merge = TRUE) + // do random amount calls BEFORE we add the mats or else the code eats shit and dies if(!amount) amount = rand(1,2) pixel_x = rand(-2,2) pixel_y = rand(-2,2) + . = ..() update_icon() /obj/item/stack/cable_coil/cut/red @@ -869,7 +870,7 @@ By design, d1 is the smallest direction and d2 is the highest /obj/item/stack/cable_coil/cut/random color = "#ffffff" -/obj/item/stack/cable_coil/cut/random/Initialize(mapload, new_amount = null, param_color = null) +/obj/item/stack/cable_coil/cut/random/Initialize(mapload, new_amount, merge = TRUE, param_color = null) . = ..() var/list/cable_colors = GLOB.cable_colors color = pick(cable_colors) diff --git a/code/modules/power/floodlight.dm b/code/modules/power/floodlight.dm index 466030b83c..274d4cd7af 100644 --- a/code/modules/power/floodlight.dm +++ b/code/modules/power/floodlight.dm @@ -9,7 +9,7 @@ var/state = FLOODLIGHT_NEEDS_WRENCHING /obj/structure/floodlight_frame/attackby(obj/item/O, mob/user, params) - if(istype(O, /obj/item/wrench) && (state == FLOODLIGHT_NEEDS_WRENCHING)) + if(O.tool_behaviour == TOOL_WRENCH && (state == FLOODLIGHT_NEEDS_WRENCHING)) to_chat(user, "You secure [src].") anchored = TRUE state = FLOODLIGHT_NEEDS_WIRES @@ -26,7 +26,7 @@ to_chat(user, "You put lights in [src].") new /obj/machinery/power/floodlight(src.loc) qdel(src) - else if(istype(O, /obj/item/screwdriver) && (state == FLOODLIGHT_NEEDS_SECURING)) + else if(O.tool_behaviour == TOOL_SCREWDRIVER && (state == FLOODLIGHT_NEEDS_SECURING)) to_chat(user, "You fasten the wiring and electronics in [src].") name = "secured [name]" desc = "A bare metal frame that looks like a floodlight. Requires light tubes." @@ -82,7 +82,7 @@ to_chat(user, "You set [src] to [setting_text].") /obj/machinery/power/floodlight/attackby(obj/item/O, mob/user, params) - if(istype(O, /obj/item/wrench)) + if(O.tool_behaviour == TOOL_WRENCH) default_unfasten_wrench(user, O, time = 20) change_setting(1) if(anchored) diff --git a/code/modules/power/generator.dm b/code/modules/power/generator.dm index bc921b5b26..9213a5d2ba 100644 --- a/code/modules/power/generator.dm +++ b/code/modules/power/generator.dm @@ -66,7 +66,10 @@ var/energy_transfer = delta_temperature*hot_air_heat_capacity*cold_air_heat_capacity/(hot_air_heat_capacity+cold_air_heat_capacity) var/heat = energy_transfer*(1-efficiency) - lastgen += LOGISTIC_FUNCTION(500000,0.0009,delta_temperature,10000) + if(delta_temperature < 16800) // second point where derivative of below function = 1 + lastgen += LOGISTIC_FUNCTION(500000,0.0009,delta_temperature,10000) + else + lastgen += delta_temperature + 482102 // value of above function at 16800, or very nearly so hot_air.set_temperature(hot_air.return_temperature() - energy_transfer/hot_air_heat_capacity) cold_air.set_temperature(cold_air.return_temperature() + heat/cold_air_heat_capacity) diff --git a/code/modules/power/lighting.dm b/code/modules/power/lighting.dm index 4c76c4b5b1..00487ee607 100644 --- a/code/modules/power/lighting.dm +++ b/code/modules/power/lighting.dm @@ -107,7 +107,7 @@ return switch(stage) if(1) - if(istype(W, /obj/item/wrench)) + if(W.tool_behaviour == TOOL_WRENCH) to_chat(usr, "You begin deconstructing [src]...") if (W.use_tool(src, user, 30, volume=50)) new /obj/item/stack/sheet/metal(drop_location(), sheets_refunded) @@ -127,11 +127,11 @@ to_chat(user, "You need one length of cable to wire [src]!") return if(2) - if(istype(W, /obj/item/wrench)) + if(W.tool_behaviour == TOOL_WRENCH) to_chat(usr, "You have to remove the wires first!") return - if(istype(W, /obj/item/wirecutters)) + if(W.tool_behaviour == TOOL_WIRECUTTER) stage = 1 icon_state = "[fixture_type]-construct-stage1" new /obj/item/stack/cable_coil(drop_location(), 1, "red") @@ -140,7 +140,7 @@ W.play_tool_sound(src, 100) return - if(istype(W, /obj/item/screwdriver)) + if(W.tool_behaviour == TOOL_SCREWDRIVER) user.visible_message("[user.name] closes [src]'s casing.", \ "You close [src]'s casing.", "You hear screwing.") W.play_tool_sound(src, 75) @@ -462,7 +462,7 @@ // attempt to stick weapon into light socket else if(status == LIGHT_EMPTY) - if(istype(W, /obj/item/screwdriver)) //If it's a screwdriver open it. + if(W.tool_behaviour == TOOL_SCREWDRIVER) //If it acts like a screwdriver, open it. W.play_tool_sound(src, 75) user.visible_message("[user.name] opens [src]'s casing.", \ "You open [src]'s casing.", "You hear a noise.") @@ -621,7 +621,7 @@ else to_chat(H, "You can't receive charge from the [fitting]!") return - + if(H.gloves) var/obj/item/clothing/gloves/G = H.gloves if(G.max_heat_protection_temperature) diff --git a/code/modules/power/singularity/containment_field.dm b/code/modules/power/singularity/containment_field.dm index dfb51df9cf..2ca9b7513c 100644 --- a/code/modules/power/singularity/containment_field.dm +++ b/code/modules/power/singularity/containment_field.dm @@ -56,12 +56,14 @@ else ..() -/obj/machinery/field/containment/Crossed(mob/mover) - if(isliving(mover)) - shock(mover) +/obj/machinery/field/containment/Crossed(atom/movable/AM) + . = ..() + if(isliving(AM)) + shock(AM) + + if(ismachinery(AM) || isstructure(AM) || ismecha(AM)) + bump_field(AM) - if(ismachinery(mover) || isstructure(mover) || ismecha(mover)) - bump_field(mover) /obj/machinery/field/containment/proc/set_master(master1,master2) if(!master1 || !master2) diff --git a/code/modules/power/singularity/generator.dm b/code/modules/power/singularity/generator.dm index 98729de3cc..f5e3bbc141 100644 --- a/code/modules/power/singularity/generator.dm +++ b/code/modules/power/singularity/generator.dm @@ -18,7 +18,7 @@ var/creation_type = /obj/singularity /obj/machinery/the_singularitygen/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/wrench)) + if(W.tool_behaviour == TOOL_WRENCH) default_unfasten_wrench(user, W, 0) else return ..() diff --git a/code/modules/power/singularity/particle_accelerator/particle.dm b/code/modules/power/singularity/particle_accelerator/particle.dm index 9e098446d1..7ecde364ae 100644 --- a/code/modules/power/singularity/particle_accelerator/particle.dm +++ b/code/modules/power/singularity/particle_accelerator/particle.dm @@ -44,6 +44,7 @@ movement_range = 0 /obj/effect/accelerated_particle/Crossed(atom/A) + . = ..() if(isliving(A)) toxmob(A) diff --git a/code/modules/power/singularity/particle_accelerator/particle_accelerator.dm b/code/modules/power/singularity/particle_accelerator/particle_accelerator.dm index 528a3abb8b..60c97eda76 100644 --- a/code/modules/power/singularity/particle_accelerator/particle_accelerator.dm +++ b/code/modules/power/singularity/particle_accelerator/particle_accelerator.dm @@ -63,7 +63,7 @@ switch(construction_state) if(PA_CONSTRUCTION_UNSECURED) - if(istype(W, /obj/item/wrench) && !isinspace()) + if(W.tool_behaviour == TOOL_WRENCH && !isinspace()) W.play_tool_sound(src, 75) anchored = TRUE user.visible_message("[user.name] secures the [name] to the floor.", \ @@ -71,7 +71,7 @@ construction_state = PA_CONSTRUCTION_UNWIRED did_something = TRUE if(PA_CONSTRUCTION_UNWIRED) - if(istype(W, /obj/item/wrench)) + if(W.tool_behaviour == TOOL_WRENCH) W.play_tool_sound(src, 75) anchored = FALSE user.visible_message("[user.name] detaches the [name] from the floor.", \ @@ -85,18 +85,18 @@ construction_state = PA_CONSTRUCTION_PANEL_OPEN did_something = TRUE if(PA_CONSTRUCTION_PANEL_OPEN) - if(istype(W, /obj/item/wirecutters))//TODO:Shock user if its on? + if(W.tool_behaviour == TOOL_WIRECUTTER)//TODO:Shock user if its on? user.visible_message("[user.name] removes some wires from the [name].", \ "You remove some wires.") construction_state = PA_CONSTRUCTION_UNWIRED did_something = TRUE - else if(istype(W, /obj/item/screwdriver)) + else if(W.tool_behaviour == TOOL_SCREWDRIVER) user.visible_message("[user.name] closes the [name]'s access panel.", \ "You close the access panel.") construction_state = PA_CONSTRUCTION_COMPLETE did_something = TRUE if(PA_CONSTRUCTION_COMPLETE) - if(istype(W, /obj/item/screwdriver)) + if(W.tool_behaviour == TOOL_SCREWDRIVER) user.visible_message("[user.name] opens the [name]'s access panel.", \ "You open the access panel.") construction_state = PA_CONSTRUCTION_PANEL_OPEN diff --git a/code/modules/power/smes.dm b/code/modules/power/smes.dm index f0f99ab7da..32e6186133 100644 --- a/code/modules/power/smes.dm +++ b/code/modules/power/smes.dm @@ -162,8 +162,8 @@ return TRUE -/obj/machinery/power/smes/default_deconstruction_crowbar(obj/item/crowbar/C) - if(istype(C) && terminal) +/obj/machinery/power/smes/default_deconstruction_crowbar(obj/item/C) + if(C.tool_behaviour == TOOL_CROWBAR && terminal) to_chat(usr, "You must first remove the power terminal!") return FALSE diff --git a/code/modules/power/solar.dm b/code/modules/power/solar.dm index cf526f083d..92aa95bfb7 100644 --- a/code/modules/power/solar.dm +++ b/code/modules/power/solar.dm @@ -1,5 +1,4 @@ #define SOLAR_GEN_RATE 1500 -#define OCCLUSION_DISTANCE 20 /obj/machinery/power/solar name = "solar panel" @@ -14,8 +13,8 @@ integrity_failure = 0.33 var/id - var/obscured = FALSE - var/sunfrac = 0 //[0-1] measure of obscuration -- multipllier against power generation + var/list/obscured = list() + var/total_flux = 0 // multipllier against power generation -- measured by obscuration of all suns var/azimuth_current = 0 //[0-360) degrees, which direction are we facing? var/azimuth_target = 0 //same but what way we're going to face next time we turn var/obj/machinery/power/solar_control/control @@ -133,40 +132,28 @@ ///trace towards sun to see if we're in shadow /obj/machinery/power/solar/proc/occlusion_setup() - obscured = TRUE - - var/distance = OCCLUSION_DISTANCE - var/target_x = round(sin(SSsun.azimuth), 0.01) - var/target_y = round(cos(SSsun.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 - if(hit.x == 1 || hit.x == world.maxx || hit.y == 1 || hit.y == world.maxy) //edge of the map - break - obscured = FALSE + obscured = list() + for(var/S in SSsun.suns) + if(check_obscured(S)) + obscured |= S ///calculates the fraction of the sunlight that the panel receives /obj/machinery/power/solar/proc/update_solar_exposure() needs_to_update_solar_exposure = FALSE - sunfrac = 0 - if(obscured) - return 0 - - var/sun_azimuth = SSsun.azimuth - if(azimuth_current == sun_azimuth) //just a quick optimization for the most frequent case - . = 1 - else - //dot product of sun and panel -- Lambert's Cosine Law - . = cos(azimuth_current - sun_azimuth) - . = clamp(round(., 0.01), 0, 1) - sunfrac = . + total_flux = 0 + for(var/S in SSsun.suns) + if(S in obscured) + continue + var/datum/sun/sun = S + var/sun_azimuth = sun.azimuth + var/cur_pow = 0 + if(azimuth_current == sun_azimuth) //just a quick optimization for the most frequent case + cur_pow = sun.power_mod + else + //dot product of sun and panel -- Lambert's Cosine Law + cur_pow = cos(azimuth_current - sun_azimuth) * sun.power_mod + cur_pow = clamp(round(cur_pow, 0.01), 0, 1) + total_flux += cur_pow /obj/machinery/power/solar/process() if(stat & BROKEN) @@ -177,10 +164,10 @@ update_turn() if(needs_to_update_solar_exposure) update_solar_exposure() - if(sunfrac <= 0) + if(total_flux <= 0) return - var/sgen = SOLAR_GEN_RATE * sunfrac * efficiency + var/sgen = SOLAR_GEN_RATE * total_flux * efficiency add_avail(sgen) if(control) control.gen += sgen @@ -227,7 +214,7 @@ /obj/item/solar_assembly/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/wrench) && isturf(loc)) + if(W.tool_behaviour == TOOL_WRENCH && isturf(loc)) if(isinspace()) to_chat(user, "You can't secure [src] here.") return @@ -245,12 +232,10 @@ to_chat(user, "You need to secure the assembly before you can add glass.") return var/obj/item/stack/sheet/S = W - var/obj/item/stack/sheet/G = S.change_stack(null, 2) - if(G) - glass_type = G - G.moveToNullspace() - playsound(src.loc, 'sound/machines/click.ogg', 50, 1) - user.visible_message("[user] places the glass on the solar assembly.", "You place the glass on the solar assembly.") + if(S.use(2)) + glass_type = W.type + playsound(src.loc, 'sound/machines/click.ogg', 50, TRUE) + user.visible_message("[user] places the glass on the solar assembly.", "You place the glass on the solar assembly.") if(tracker) new /obj/machinery/power/tracker(get_turf(src), src) else @@ -258,7 +243,7 @@ else to_chat(user, "You need two sheets of glass to put them into a solar panel!") return - return 1 + return TRUE if(!tracker) if(istype(W, /obj/item/electronics/tracker)) @@ -269,7 +254,7 @@ user.visible_message("[user] inserts the electronics into the solar assembly.", "You insert the electronics into the solar assembly.") return 1 else - if(istype(W, /obj/item/crowbar)) + if(W.tool_behaviour == TOOL_CROWBAR) new /obj/item/electronics/tracker(src.loc) tracker = 0 user.visible_message("[user] takes out the electronics from the solar assembly.", "You take out the electronics from the solar assembly.") @@ -387,7 +372,7 @@ track = mode if(mode == SOLAR_TRACK_AUTO) if(connected_tracker) - connected_tracker.sun_update(SSsun, SSsun.azimuth) + connected_tracker.sun_update(SSsun, SSsun.primary_sun, SSsun.suns) else track = SOLAR_TRACK_OFF return TRUE @@ -485,4 +470,3 @@ Congratulations, you should have a working solar array. If you are having troubl "} #undef SOLAR_GEN_RATE -#undef OCCLUSION_DISTANCE diff --git a/code/modules/power/supermatter/supermatter.dm b/code/modules/power/supermatter/supermatter.dm index f385b640aa..1e82a601ea 100644 --- a/code/modules/power/supermatter/supermatter.dm +++ b/code/modules/power/supermatter/supermatter.dm @@ -348,11 +348,11 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) /obj/machinery/power/supermatter_crystal/proc/alarm() switch(get_status()) if(SUPERMATTER_DELAMINATING) - playsound(src, 'sound/misc/bloblarm.ogg', 100) + playsound(src, 'sound/misc/bloblarm.ogg', 100, FALSE, 40, 30, falloff_distance = 10) if(SUPERMATTER_EMERGENCY) - playsound(src, 'sound/machines/engine_alert1.ogg', 100) + playsound(src, 'sound/machines/engine_alert1.ogg', 100, FALSE, 30, 30, falloff_distance = 10) if(SUPERMATTER_DANGER) - playsound(src, 'sound/machines/engine_alert2.ogg', 100) + playsound(src, 'sound/machines/engine_alert2.ogg', 100, FALSE, 30, 30, falloff_distance = 10) if(SUPERMATTER_WARNING) playsound(src, 'sound/machines/terminal_alert.ogg', 75) diff --git a/code/modules/power/tesla/coil.dm b/code/modules/power/tesla/coil.dm index be24ea0fef..13f2aab811 100644 --- a/code/modules/power/tesla/coil.dm +++ b/code/modules/power/tesla/coil.dm @@ -131,8 +131,10 @@ else . = ..() -/obj/machinery/power/tesla_coil/research/default_unfasten_wrench(mob/user, obj/item/wrench/W, time = 20) +/obj/machinery/power/tesla_coil/research/default_unfasten_wrench(mob/user, obj/item/W, time = 20) . = ..() + if(!W.tool_behaviour == TOOL_WRENCH) + return if(. == SUCCESSFUL_UNFASTEN) if(panel_open) icon_state = "rpcoil_open[anchored]" diff --git a/code/modules/power/tracker.dm b/code/modules/power/tracker.dm index 0627a55de0..86168979c2 100644 --- a/code/modules/power/tracker.dm +++ b/code/modules/power/tracker.dm @@ -41,10 +41,10 @@ control = null ///Tell the controller to turn the solar panels -/obj/machinery/power/tracker/proc/sun_update(datum/source, azimuth) - setDir(angle2dir(azimuth)) +/obj/machinery/power/tracker/proc/sun_update(datum/source, datum/sun/primary_sun, list/datum/sun/suns) + setDir(angle2dir(primary_sun.azimuth)) if(control && control.track == SOLAR_TRACK_AUTO) - control.set_panels(azimuth) + control.set_panels(primary_sun.azimuth) /obj/machinery/power/tracker/proc/Make(obj/item/solar_assembly/S) if(!S) diff --git a/code/modules/projectiles/ammunition/ballistic/shotgun.dm b/code/modules/projectiles/ammunition/ballistic/shotgun.dm index 68c1c1036d..2a41b41d1d 100644 --- a/code/modules/projectiles/ammunition/ballistic/shotgun.dm +++ b/code/modules/projectiles/ammunition/ballistic/shotgun.dm @@ -8,7 +8,7 @@ projectile_type = /obj/item/projectile/bullet/shotgun_slug custom_materials = list(/datum/material/iron=4000) -obj/item/ammo_casing/shotgun/executioner +/obj/item/ammo_casing/shotgun/executioner name = "executioner slug" desc = "A 12 gauge lead slug purpose built to annihilate flesh on impact." icon_state = "stunshell" diff --git a/code/modules/projectiles/ammunition/caseless/foam.dm b/code/modules/projectiles/ammunition/caseless/foam.dm index 91ed69f7a5..ac0358e220 100644 --- a/code/modules/projectiles/ammunition/caseless/foam.dm +++ b/code/modules/projectiles/ammunition/caseless/foam.dm @@ -24,13 +24,13 @@ /obj/item/ammo_casing/caseless/foam_dart/attackby(obj/item/A, mob/user, params) var/obj/item/projectile/bullet/reusable/foam_dart/FD = BB - if (istype(A, /obj/item/screwdriver) && !modified) + if(A.tool_behaviour == TOOL_SCREWDRIVER && !modified) modified = TRUE FD.modified = TRUE FD.damage_type = BRUTE to_chat(user, "You pop the safety cap off [src].") update_icon() - else if (istype(A, /obj/item/pen)) + else if(istype(A, /obj/item/pen)) if(modified) if(!FD.pen) harmful = TRUE diff --git a/code/modules/projectiles/ammunition/energy/special.dm b/code/modules/projectiles/ammunition/energy/special.dm index fc4207d084..2bf7c06ec6 100644 --- a/code/modules/projectiles/ammunition/energy/special.dm +++ b/code/modules/projectiles/ammunition/energy/special.dm @@ -24,6 +24,11 @@ projectile_type = /obj/item/projectile/energy/floramut select_name = "mutation" +/obj/item/ammo_casing/energy/flora/revolution + projectile_type = /obj/item/projectile/energy/florarevolution + select_name = "revolution" + e_cost = 250 + /obj/item/ammo_casing/energy/temp projectile_type = /obj/item/projectile/temp select_name = "freeze" diff --git a/code/modules/projectiles/boxes_magazines/external/rechargable.dm b/code/modules/projectiles/boxes_magazines/external/rechargable.dm index 76b2102731..5b774b111a 100644 --- a/code/modules/projectiles/boxes_magazines/external/rechargable.dm +++ b/code/modules/projectiles/boxes_magazines/external/rechargable.dm @@ -94,14 +94,15 @@ /obj/item/ammo_casing/mws_batt/lethal name = "'MWS' microbattery - LETHAL" type_color = "#bf3d3d" - type_name = "LETHAL" + type_name = "LASE" projectile_type = /obj/item/projectile/beam /obj/item/ammo_casing/mws_batt/stun - name = "'MWS' microbattery - STUN" + name = "'MWS' microbattery - DISABLER" type_color = "#0f81bc" - type_name = "STUN" + type_name = "DISABLE" projectile_type = /obj/item/projectile/beam/disabler + e_cost = 60 //gives it 10 disabler shots in line with literally all other eguns. /obj/item/ammo_casing/mws_batt/xray name = "'MWS' microbattery - XRAY" @@ -114,3 +115,9 @@ type_color = "#d084d6" type_name = "ION" projectile_type = /obj/item/projectile/ion + +/obj/item/ammo_casing/mws_batt/taser + name = "'MWS' microbattery - TASER" + type_color = "#e5ff00" + type_name = "TASE" + projectile_type = /obj/item/projectile/energy/electrode/security diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm index 1c3a0d230f..63d019f97f 100644 --- a/code/modules/projectiles/gun.dm +++ b/code/modules/projectiles/gun.dm @@ -18,6 +18,8 @@ item_flags = NEEDS_PERMIT attack_verb = list("struck", "hit", "bashed") attack_speed = CLICK_CD_RANGE + var/ranged_attack_speed = CLICK_CD_RANGE + var/melee_attack_speed = CLICK_CD_MELEE var/fire_sound = "gunshot" var/suppressed = null //whether or not a message is displayed when fired @@ -158,7 +160,7 @@ user.UseStaminaBuffer(safe_cost) if(suppressed) - playsound(user, fire_sound, 10, 1) + playsound(user, fire_sound, 10, TRUE, ignore_walls = FALSE, extrarange = SILENCED_SOUND_EXTRARANGE, falloff_distance = 0) else playsound(user, fire_sound, 50, 1) if(message) @@ -173,12 +175,25 @@ for(var/obj/O in contents) O.emp_act(severity) +/obj/item/gun/attack(mob/living/M, mob/user) + . = ..() + if(!(. & DISCARD_LAST_ACTION)) + user.DelayNextAction(melee_attack_speed) + +/obj/item/gun/attack_obj(obj/O, mob/user) + . = ..() + if(!(. & DISCARD_LAST_ACTION)) + user.DelayNextAction(melee_attack_speed) + /obj/item/gun/afterattack(atom/target, mob/living/user, flag, params) . = ..() - if(!CheckAttackCooldown(user, target)) + if(!CheckAttackCooldown(user, target, TRUE)) return process_afterattack(target, user, flag, params) +/obj/item/gun/CheckAttackCooldown(mob/user, atom/target, shooting = FALSE) + return user.CheckActionCooldown(shooting? ranged_attack_speed : attack_speed, clickdelay_from_next_action, clickdelay_mod_bypass, clickdelay_ignores_next_action) + /obj/item/gun/proc/process_afterattack(atom/target, mob/living/user, flag, params) if(!target) return @@ -419,7 +434,7 @@ to_chat(user, "You attach \the [K] to the front of \the [src].") bayonet = K update_icon() - else if(istype(I, /obj/item/screwdriver)) + else if(I.tool_behaviour == TOOL_SCREWDRIVER) if(gun_light) var/obj/item/flashlight/seclite/S = gun_light to_chat(user, "You unscrew the seclite from \the [src].") diff --git a/code/modules/projectiles/guns/ballistic/automatic.dm b/code/modules/projectiles/guns/ballistic/automatic.dm index 00e59d09f8..e8d53ddeb8 100644 --- a/code/modules/projectiles/guns/ballistic/automatic.dm +++ b/code/modules/projectiles/guns/ballistic/automatic.dm @@ -12,6 +12,7 @@ name = "\improper Nanotrasen Saber SMG" desc = "A prototype three-round burst 9mm submachine gun, designated 'SABR'. Has a threaded barrel for suppressors." icon_state = "saber" + fire_sound = "sound/weapons/gunshot_smg_alt.ogg" mag_type = /obj/item/ammo_box/magazine/smgm9mm pin = null @@ -125,6 +126,7 @@ desc = "An outdated personal defence weapon. Uses 4.6x30mm rounds and is designated the WT-550 Semi-Automatic SMG." icon_state = "wt550" item_state = "arg" + fire_sound = "sound/weapons/gunshot_smg_alt.ogg" mag_type = /obj/item/ammo_box/magazine/wt550m9 can_suppress = FALSE burst_size = 2 @@ -138,6 +140,10 @@ . = ..() spread = 15 +/obj/item/gun/ballistic/automatic/wt550/afterattack() + . = ..() + empty_alarm() + /obj/item/gun/ballistic/automatic/wt550/disable_burst() . = ..() spread = 0 @@ -158,7 +164,7 @@ icon_state = "m90" item_state = "m90" mag_type = /obj/item/ammo_box/magazine/m556 - fire_sound = 'sound/weapons/gunshot_smg.ogg' + fire_sound = 'sound/weapons/rifleshot.ogg' can_suppress = FALSE automatic_burst_overlay = FALSE var/obj/item/gun/ballistic/revolver/grenadelauncher/underbarrel @@ -243,7 +249,7 @@ item_state = "arg" slot_flags = 0 mag_type = /obj/item/ammo_box/magazine/m556 - fire_sound = 'sound/weapons/gunshot_smg.ogg' + fire_sound = 'sound/weapons/rifleshot.ogg' can_suppress = FALSE burst_size = 3 burst_shot_delay = 1 @@ -258,7 +264,7 @@ w_class = WEIGHT_CLASS_NORMAL weapon_weight = WEAPON_MEDIUM mag_type = /obj/item/ammo_box/magazine/m12g - fire_sound = 'sound/weapons/gunshot.ogg' + fire_sound = 'sound/weapons/gunshotshotgunshot.ogg' automatic_burst_overlay = FALSE can_suppress = FALSE burst_size = 1 @@ -293,11 +299,11 @@ desc = "A heavily modified 1.95x129mm light machine gun, designated 'L6 SAW'. Has 'Aussec Armoury - 2531' engraved on the receiver below the designation." icon_state = "l6closed100" item_state = "l6closedmag" + fire_sound = "sound/weapons/lmgshot.ogg" w_class = WEIGHT_CLASS_HUGE slot_flags = 0 mag_type = /obj/item/ammo_box/magazine/mm195x129 weapon_weight = WEAPON_HEAVY - fire_sound = 'sound/weapons/gunshot_smg.ogg' var/cover_open = FALSE can_suppress = FALSE burst_size = 3 @@ -363,6 +369,7 @@ desc = "A long ranged weapon that does significant damage. No, you can't quickscope." icon_state = "sniper" item_state = "sniper" + fire_sound = "sound/weapons/noscope.ogg" recoil = 2 weapon_weight = WEAPON_HEAVY mag_type = /obj/item/ammo_box/magazine/sniper_rounds @@ -397,6 +404,7 @@ desc = "One of countless obsolete ballistic rifles that still sees use as a cheap deterrent. Uses 10mm ammo and its bulky frame prevents one-hand firing." icon_state = "surplus" item_state = "moistnugget" + fire_sound = 'sound/weapons/rifleshot.ogg' weapon_weight = WEAPON_HEAVY mag_type = /obj/item/ammo_box/magazine/m10mm/rifle fire_delay = 30 diff --git a/code/modules/projectiles/guns/ballistic/launchers.dm b/code/modules/projectiles/guns/ballistic/launchers.dm index 9e03207888..c53366c4f5 100644 --- a/code/modules/projectiles/guns/ballistic/launchers.dm +++ b/code/modules/projectiles/guns/ballistic/launchers.dm @@ -35,7 +35,7 @@ name = "gyrojet pistol" desc = "A prototype pistol designed to fire self propelled rockets." icon_state = "gyropistol" - fire_sound = 'sound/weapons/grenadelaunch.ogg' + fire_sound = 'sound/weapons/rocketlaunch.ogg' mag_type = /obj/item/ammo_box/magazine/m75 burst_size = 1 fire_delay = 0 diff --git a/code/modules/projectiles/guns/ballistic/pistol.dm b/code/modules/projectiles/guns/ballistic/pistol.dm index 9ed9630f2f..98b654aadb 100644 --- a/code/modules/projectiles/guns/ballistic/pistol.dm +++ b/code/modules/projectiles/guns/ballistic/pistol.dm @@ -138,7 +138,7 @@ can_suppress = FALSE w_class = WEIGHT_CLASS_NORMAL actions_types = list() - fire_sound = 'sound/weapons/blastcannon.ogg' + fire_sound = 'sound/weapons/noscope.ogg' spread = 20 //damn thing has no rifling. automatic_burst_overlay = FALSE diff --git a/code/modules/projectiles/guns/ballistic/revolver.dm b/code/modules/projectiles/guns/ballistic/revolver.dm index 3e564a9ace..0d60203cb5 100644 --- a/code/modules/projectiles/guns/ballistic/revolver.dm +++ b/code/modules/projectiles/guns/ballistic/revolver.dm @@ -3,6 +3,7 @@ desc = "A suspicious revolver. Uses .357 ammo." //usually used by syndicates icon_state = "revolver" mag_type = /obj/item/ammo_box/magazine/internal/cylinder + fire_sound = "sound/weapons/revolvershot.ogg" casing_ejector = FALSE /obj/item/gun/ballistic/revolver/Initialize() @@ -338,7 +339,9 @@ slot_flags = null mag_type = /obj/item/ammo_box/magazine/internal/shot/improvised sawn_desc = "I'm just here for the gasoline." - unique_reskin = null + unique_reskin = list("Default" = "ishotgun", + "Cobbled" = "old_ishotgun" + ) var/slung = FALSE /obj/item/gun/ballistic/revolver/doublebarrel/improvised/attackby(obj/item/A, mob/user, params) @@ -389,16 +392,12 @@ // ---------- Code originally from VoreStation ---------- /obj/item/gun/ballistic/revolver/mws name = "MWS-01 'Big Iron'" - desc = "Modular Weapons System" - + desc = "Modular Weapon System-01, does fit on your hip." icon = 'icons/obj/guns/projectile.dmi' icon_state = "mws" - - fire_sound = 'sound/weapons/Taser.ogg' - + fire_sound = 'sound/weapons/MWSfire.ogg' //i spent 1 hour making a cool sound but byond just compresses it to shit so have this instead >:( mag_type = /obj/item/ammo_box/magazine/mws_mag spawnwithmagazine = FALSE - recoil = 0 var/charge_sections = 6 diff --git a/code/modules/projectiles/guns/ballistic/shotgun.dm b/code/modules/projectiles/guns/ballistic/shotgun.dm index 796e544603..8035440906 100644 --- a/code/modules/projectiles/guns/ballistic/shotgun.dm +++ b/code/modules/projectiles/guns/ballistic/shotgun.dm @@ -3,6 +3,7 @@ desc = "A traditional shotgun with wood furniture and a four-shell capacity underneath." icon_state = "shotgun" item_state = "shotgun" + fire_sound = "sound/weapons/gunshotshotgunshot.ogg" w_class = WEIGHT_CLASS_BULKY force = 10 flags_1 = CONDUCT_1 diff --git a/code/modules/projectiles/guns/energy/kinetic_accelerator.dm b/code/modules/projectiles/guns/energy/kinetic_accelerator.dm index 438c000a1e..1e6318ecba 100644 --- a/code/modules/projectiles/guns/energy/kinetic_accelerator.dm +++ b/code/modules/projectiles/guns/energy/kinetic_accelerator.dm @@ -182,6 +182,7 @@ /obj/item/gun/energy/kinetic_accelerator/proc/reload() cell.give(cell.maxcharge) + process_chamber() if(!suppressed) playsound(src.loc, 'sound/weapons/kenetic_reload.ogg', 60, 1) else diff --git a/code/modules/projectiles/guns/energy/special.dm b/code/modules/projectiles/guns/energy/special.dm index a08e570dd7..f965100846 100644 --- a/code/modules/projectiles/guns/energy/special.dm +++ b/code/modules/projectiles/guns/energy/special.dm @@ -45,7 +45,7 @@ desc = "A tool that discharges controlled radiation which induces mutation in plant cells." icon_state = "flora" item_state = "gun" - ammo_type = list(/obj/item/ammo_casing/energy/flora/yield, /obj/item/ammo_casing/energy/flora/mut) + ammo_type = list(/obj/item/ammo_casing/energy/flora/yield, /obj/item/ammo_casing/energy/flora/mut, /obj/item/ammo_casing/energy/flora/revolution) modifystate = 1 ammo_x_offset = 1 selfcharge = EGUN_SELFCHARGE @@ -173,18 +173,60 @@ force = 15 ammo_type = list(/obj/item/ammo_casing/energy/plasma/adv) +//Sci guns + +/obj/item/gun/energy/gravity_gun + name = "one-point gravitational manipulator" + desc = "An experimental, multi-mode device that fires bolts of Zero-Point Energy, causing local distortions in gravity. Requires an anomaly core to function." + ammo_type = list(/obj/item/ammo_casing/energy/gravity/repulse, /obj/item/ammo_casing/energy/gravity/attract, /obj/item/ammo_casing/energy/gravity/chaos) + item_state = "gravity_gun" + icon_state = "gravity_gun" + var/power = 4 + var/firing_core = FALSE + +/obj/item/gun/energy/gravity_gun/attackby(obj/item/C, mob/user) + if(istype(C, /obj/item/assembly/signaler/anomaly)) + to_chat(user, "You insert [C] into the gravitational manipulator and the weapon gently hums to life.") + firing_core = TRUE + playsound(src.loc, 'sound/machines/click.ogg', 50, TRUE) + qdel(C) + return + return ..() + +/obj/item/gun/energy/gravity_gun/can_shoot() + if(!firing_core) + return FALSE + return ..() + /obj/item/gun/energy/wormhole_projector name = "bluespace wormhole projector" - desc = "A projector that emits high density quantum-coupled bluespace beams." + desc = "A projector that emits high density quantum-coupled bluespace beams. Requires an anomaly core to function." ammo_type = list(/obj/item/ammo_casing/energy/wormhole, /obj/item/ammo_casing/energy/wormhole/orange) item_state = null icon_state = "wormhole_projector" - pin = null inaccuracy_modifier = 0.25 automatic_charge_overlays = FALSE var/obj/effect/portal/p_blue var/obj/effect/portal/p_orange var/atmos_link = FALSE + var/firing_core = FALSE + +/obj/item/gun/energy/wormhole_projector/attackby(obj/item/C, mob/user) + if(istype(C, /obj/item/assembly/signaler/anomaly)) + to_chat(user, "You insert [C] into the wormhole projector and the weapon gently hums to life.") + firing_core = TRUE + playsound(src.loc, 'sound/machines/click.ogg', 50, TRUE) + qdel(C) + return + +/obj/item/gun/energy/wormhole_projector/can_shoot() + if(!firing_core) + return FALSE + return ..() + +/obj/item/gun/energy/wormhole_projector/shoot_with_empty_chamber(mob/living/user) + . = ..() + to_chat(user, "The display says, 'NO CORE INSTALLED'.") /obj/item/gun/energy/wormhole_projector/update_icon_state() icon_state = "[initial(icon_state)][current_firemode_index]" @@ -243,6 +285,9 @@ p_blue = P crosslink() +/obj/item/gun/energy/wormhole_projector/core_inserted + firing_core = TRUE + /* 3d printer 'pseudo guns' for borgs */ /obj/item/gun/energy/printer @@ -298,18 +343,6 @@ /obj/item/gun/energy/laser/instakill/emp_act() //implying you could stop the instagib return -/obj/item/gun/energy/gravity_gun - name = "one-point bluespace-gravitational manipulator" - desc = "An experimental, multi-mode device that fires bolts of Zero-Point Energy, causing local distortions in gravity." - ammo_type = list(/obj/item/ammo_casing/energy/gravity/repulse, /obj/item/ammo_casing/energy/gravity/attract, /obj/item/ammo_casing/energy/gravity/chaos) - item_state = "gravity_gun" - icon_state = "gravity_gun" - pin = null - var/power = 4 - -/obj/item/gun/energy/gravity_gun/security - pin = /obj/item/firing_pin - //Emitter Gun /obj/item/gun/energy/emitter diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm index fb9e6ff6c6..481d3c2c68 100644 --- a/code/modules/projectiles/projectile.dm +++ b/code/modules/projectiles/projectile.dm @@ -471,11 +471,10 @@ if(paused || !isturf(loc)) return - var/ds = (SSprojectiles.flags & SS_TICKER)? (wait * world.tick_lag) : wait - var/required_pixels = (pixels_per_second * ds * 0.1) + pixels_tick_leftover + var/required_pixels = (pixels_per_second * wait) + pixels_tick_leftover if(required_pixels >= pixel_increment_amount) pixels_tick_leftover = MODULUS(required_pixels, pixel_increment_amount) - pixel_move(FLOOR(required_pixels / pixel_increment_amount, 1), FALSE, ds, SSprojectiles.global_projectile_speed_multiplier) + pixel_move(FLOOR(required_pixels / pixel_increment_amount, 1), FALSE, wait, SSprojectiles.global_projectile_speed_multiplier) else pixels_tick_leftover = required_pixels @@ -603,7 +602,7 @@ * Trajectory multiplier directly modifies the factor of pixel_increment_amount to go per time. * It's complicated, so probably just don't mess with this unless you know what you're doing. */ -/obj/item/projectile/proc/pixel_move(times, hitscanning = FALSE, deciseconds_equivalent = world.tick_lag, trajectory_multiplier = 1, allow_animation = TRUE) +/obj/item/projectile/proc/pixel_move(times, hitscanning = FALSE, seconds_equivalent = world.tick_lag * 0.1, trajectory_multiplier = 1, allow_animation = TRUE) if(!loc || !trajectory) return if(!nondirectional_sprite && !hitscanning) @@ -620,7 +619,7 @@ if(homing_target) // No datum/points, too expensive. var/angle = closer_angle_difference(Angle, get_projectile_angle(src, homing_target)) - var/max_turn = homing_turn_speed * deciseconds_equivalent * 0.1 + var/max_turn = homing_turn_speed * seconds_equivalent setAngle(Angle + clamp(angle, -max_turn, max_turn)) // HOMING END trajectory.increment(trajectory_multiplier) diff --git a/code/modules/projectiles/projectile/plasma.dm b/code/modules/projectiles/projectile/plasma.dm index 838dac34a0..474a3d95c7 100644 --- a/code/modules/projectiles/projectile/plasma.dm +++ b/code/modules/projectiles/projectile/plasma.dm @@ -1,4 +1,4 @@ -obj/item/projectile/energy/plasmabolt +/obj/item/projectile/energy/plasmabolt name = "plasma bolt" icon_state = "plasma" flag = "energy" diff --git a/code/modules/projectiles/projectile/reusable/foam_dart.dm b/code/modules/projectiles/projectile/reusable/foam_dart.dm index 7d21f663c2..b00c35cd18 100644 --- a/code/modules/projectiles/projectile/reusable/foam_dart.dm +++ b/code/modules/projectiles/projectile/reusable/foam_dart.dm @@ -20,8 +20,9 @@ newcasing.modified = modified var/obj/item/projectile/bullet/reusable/foam_dart/newdart = newcasing.BB newdart.modified = modified - newdart.damage = damage - newdart.nodamage = nodamage + if(modified) + newdart.damage = 5 + newdart.nodamage = FALSE newdart.damage_type = damage_type if(pen) newdart.pen = pen diff --git a/code/modules/projectiles/projectile/special/floral.dm b/code/modules/projectiles/projectile/special/floral.dm index c3fe1f0fcb..b855322f09 100644 --- a/code/modules/projectiles/projectile/special/floral.dm +++ b/code/modules/projectiles/projectile/special/floral.dm @@ -2,7 +2,7 @@ name = "alpha somatoray" icon_state = "energy" damage = 0 - damage_type = TOX + damage_type = TRUE nodamage = 1 flag = "energy" @@ -21,5 +21,13 @@ icon_state = "energy2" damage = 0 damage_type = TOX - nodamage = 1 + nodamage = TRUE + flag = "energy" + +/obj/item/projectile/energy/florarevolution + name = "gamma somatorary" + icon_state = "energy3" + damage = 0 + damage_type = TOX + nodamage = TRUE flag = "energy" diff --git a/code/modules/reagents/chemistry/holder.dm b/code/modules/reagents/chemistry/holder.dm index c489edf88e..fd6204a8e3 100644 --- a/code/modules/reagents/chemistry/holder.dm +++ b/code/modules/reagents/chemistry/holder.dm @@ -973,10 +973,8 @@ for(var/A in cached_reagents) var/datum/reagent/R = A if (R.type == reagent) - if((total_volume - amount) <= 0)//Because this can result in 0, I don't want it to crash. - pH = REAGENT_NORMAL_PH //In practice this is really confusing and players feel like it randomly melts their beakers, but I'm not sure how else to handle it. We'll see how it goes and I can remove this if it confuses people. - else if (!ignore_pH) + if(!ignore_pH) //if (((pH > R.pH) && (pH <= 7)) || ((pH < R.pH) && (pH >= 7))) pH = (((pH - R.pH) / total_volume) * amount) + pH if(istype(my_atom, /obj/item/reagent_containers/)) @@ -987,6 +985,8 @@ amount = clamp(amount, 0, R.volume) R.volume -= amount update_total() + if(total_volume <= 0)//Because this can result in 0, I don't want it to crash. + pH = REAGENT_NORMAL_PH if(!safety)//So it does not handle reactions when it need not to handle_reactions() if(my_atom) diff --git a/code/modules/reagents/chemistry/machinery/chem_dispenser.dm b/code/modules/reagents/chemistry/machinery/chem_dispenser.dm index d1c18f0510..22aadc06f4 100644 --- a/code/modules/reagents/chemistry/machinery/chem_dispenser.dm +++ b/code/modules/reagents/chemistry/machinery/chem_dispenser.dm @@ -24,9 +24,11 @@ circuit = /obj/item/circuitboard/machine/chem_dispenser var/obj/item/stock_parts/cell/cell var/powerefficiency = 0.0666666 + var/dispenceUnit = 5 var/amount = 30 var/recharge_amount = 10 var/recharge_counter = 0 + var/canStore = TRUE//If this can hold reagents or not var/mutable_appearance/beaker_overlay var/working_state = "dispenser_working" var/nopower_state = "dispenser_nopower" @@ -102,6 +104,7 @@ if(upgrade_reagents3) upgrade_reagents3 = sortList(upgrade_reagents3, /proc/cmp_reagents_asc) dispensable_reagents = sortList(dispensable_reagents, /proc/cmp_reagents_asc) + create_reagents(200, NO_REACT) update_icon() /obj/machinery/chem_dispenser/Destroy() @@ -190,24 +193,27 @@ data["amount"] = amount data["energy"] = cell.charge ? cell.charge * powerefficiency : "0" //To prevent NaN in the UI. data["maxEnergy"] = cell.maxcharge * powerefficiency + data["storedVol"] = reagents.total_volume + data["maxVol"] = reagents.maximum_volume data["isBeakerLoaded"] = beaker ? 1 : 0 + data["stepAmount"] = dispenceUnit + data["canStore"] = canStore var/beakerContents[0] var/beakerCurrentVolume = 0 if(beaker && beaker.reagents && beaker.reagents.reagent_list.len) for(var/datum/reagent/R in beaker.reagents.reagent_list) - beakerContents.Add(list(list("name" = R.name, "volume" = R.volume))) // list in a list because Byond merges the first list... + beakerContents.Add(list(list("name" = R.name, "id" = R.type, "volume" = round(R.volume, 0.01)))) // list in a list because Byond merges the first list... beakerCurrentVolume += R.volume data["beakerContents"] = beakerContents if (beaker) - data["beakerCurrentVolume"] = beakerCurrentVolume + data["beakerCurrentVolume"] = round(beakerCurrentVolume, 0.01) data["beakerMaxVolume"] = beaker.volume data["beakerTransferAmounts"] = beaker.possible_transfer_amounts - data["beakerCurrentpH"] = beaker.reagents.pH //pH accuracy for(var/obj/item/stock_parts/capacitor/C in component_parts) - data["partRating"]= 10**(C.rating-1) + data["beakerCurrentpH"] = round(beaker.reagents.pH, 10**-(C.rating+1)) else data["beakerCurrentVolume"] = null @@ -225,11 +231,17 @@ var/chemname = temp.name if(is_hallucinating && prob(5)) chemname = "[pick_list_replacements("hallucination.json", "chemicals")]" - chemicals.Add(list(list("title" = chemname, "id" = ckey(temp.name)))) + chemicals.Add(list(list("title" = chemname, "id" = ckey(temp.name), "pH" = temp.pH, "pHCol" = ConvertpHToCol(temp.pH)))) data["chemicals"] = chemicals data["recipes"] = saved_recipes data["recordingRecipe"] = recording_recipe + + var/storedContents[0] + if(reagents.total_volume) + for(var/datum/reagent/N in reagents.reagent_list) + storedContents.Add(list(list("name" = N.name, "id" = N.type, "volume" = N.volume))) + data["storedContents"] = storedContents return data /obj/machinery/chem_dispenser/ui_act(action, params) @@ -240,10 +252,9 @@ if(!is_operational() || QDELETED(beaker)) return var/target = text2num(params["target"]) - if(target in beaker.possible_transfer_amounts) - amount = target - work_animation() - . = TRUE + SetAmount(target) + work_animation() + . = TRUE if("dispense") if(!is_operational() || QDELETED(cell)) return @@ -269,10 +280,9 @@ if(!is_operational() || recording_recipe) return var/amount = text2num(params["amount"]) - if(beaker && (amount in beaker.possible_transfer_amounts)) - beaker.reagents.remove_all(amount) - work_animation() - . = TRUE + beaker.reagents.remove_all(amount) //This should be set correctly in "amount" + work_animation() + . = TRUE if("eject") replace_beaker(usr) . = TRUE @@ -350,6 +360,52 @@ recording_recipe = null . = TRUE + //Storing and unstoring reagents + if("store") + if(!is_operational() || QDELETED(cell)) + return + if(!beaker) + return + if(recording_recipe) + say("Cannot store while recording!") + return + if(beaker.reagents.fermiIsReacting) + say("Cannot store ongoing reactions!") + return + var/reagent = text2path(params["id"]) + var/datum/reagent/R = beaker.reagents.has_reagent(reagent) + var/potentialAmount = min(amount, R.volume) + if(reagents.total_volume+potentialAmount > reagents.maximum_volume) + say("Not enough storage space left!") + return + beaker.reagents.trans_id_to(src, R.type, potentialAmount) + work_animation() + . = TRUE + + if("unstore") + if(!is_operational() || QDELETED(cell)) + return + if(!beaker) + return + if(recording_recipe) + say("Cannot distribute while recording!") + return + var/reagent = text2path(params["id"]) + var/datum/reagent/R = reagents.has_reagent(reagent) + reagents.trans_id_to(beaker, R.type, amount) + work_animation() + . = TRUE + +/obj/machinery/chem_dispenser/proc/SetAmount(inputAmount) + if(inputAmount % 5 == 0) //Always allow 5u values + amount = inputAmount + return + inputAmount -= inputAmount % dispenceUnit + if(inputAmount == 0) //Prevent ghost entries in macros + amount = dispenceUnit + return + amount = inputAmount + /obj/machinery/chem_dispenser/attackby(obj/item/I, mob/user, params) if(default_unfasten_wrench(user, I)) return @@ -402,6 +458,8 @@ cell = P for(var/obj/item/stock_parts/matter_bin/M in component_parts) newpowereff += 0.0166666666*M.rating + if(reagents) + reagents.maximum_volume = 200*(M.rating) for(var/obj/item/stock_parts/capacitor/C in component_parts) recharge_amount *= C.rating for(var/obj/item/stock_parts/manipulator/M in component_parts) @@ -411,6 +469,15 @@ dispensable_reagents |= upgrade_reagents2 if(M.rating > 3) dispensable_reagents |= upgrade_reagents3 + switch(M.rating) + if(-INFINITY to 1) + dispenceUnit = 5 + if(2) + dispenceUnit = 3 + if(3) + dispenceUnit = 2 + if(4 to INFINITY) + dispenceUnit = 1 powerefficiency = round(newpowereff, 0.01) /obj/machinery/chem_dispenser/proc/replace_beaker(mob/living/user, obj/item/reagent_containers/new_beaker) @@ -421,6 +488,8 @@ user.put_in_hands(B) if(new_beaker) beaker = new_beaker + if(amount > beaker.reagents.maximum_volume) + amount = beaker.reagents.maximum_volume else beaker = null update_icon() @@ -439,6 +508,32 @@ replace_beaker(user) return TRUE +/obj/machinery/chem_dispenser/proc/ConvertpHToCol(pH) + switch(pH) + if(-INFINITY to 1) + return "red" + if(1 to 2) + return "orange" + if(2 to 3) + return "average" + if(3 to 4) + return "yellow" + if(4 to 5) + return "olive" + if(5 to 6) + return "good" + if(6 to 8) + return "green" + if(8 to 9.5) + return "teal" + if(9.5 to 11) + return "blue" + if(11 to 12.5) + return "violet" + if(12.5 to INFINITY) + return "purple" + + /obj/machinery/chem_dispenser/drinks/Initialize() . = ..() AddComponent(/datum/component/simple_rotation, ROTATION_ALTCLICK | ROTATION_CLOCKWISE) @@ -466,6 +561,7 @@ b_o.pixel_x = rand(-9, 9) return b_o + /obj/machinery/chem_dispenser/drinks name = "soda dispenser" desc = "Contains a large reservoir of soft drinks." @@ -479,6 +575,7 @@ working_state = null nopower_state = null pass_flags = PASSTABLE + canStore = FALSE dispensable_reagents = list( /datum/reagent/water, /datum/reagent/consumable/ice, @@ -611,12 +708,14 @@ dispensable_reagents = list(/datum/reagent/toxin/mutagen) upgrade_reagents = null emagged_reagents = list(/datum/reagent/toxin/plasma) + canStore = FALSE /obj/machinery/chem_dispenser/mutagensaltpeter name = "botanical chemical dispenser" desc = "Creates and dispenses chemicals useful for botany." flags_1 = NODECONSTRUCT_1 + canStore = FALSE dispensable_reagents = list( /datum/reagent/toxin/mutagen, @@ -739,6 +838,7 @@ working_state = "minidispenser_working" nopower_state = "minidispenser_nopower" circuit = /obj/item/circuitboard/machine/chem_dispenser/apothecary + canStore = FALSE powerefficiency = 0.0833333 dispensable_reagents = list( //radium and stable plasma moved to upgrade tier 1 and 2, they've little to do with most medicines anyway. /datum/reagent/hydrogen, diff --git a/code/modules/reagents/chemistry/machinery/chem_heater.dm b/code/modules/reagents/chemistry/machinery/chem_heater.dm index 8c3b9c7f99..cd8ee2d986 100644 --- a/code/modules/reagents/chemistry/machinery/chem_heater.dm +++ b/code/modules/reagents/chemistry/machinery/chem_heater.dm @@ -126,7 +126,7 @@ var beakerContents[0] if(beaker) for(var/datum/reagent/R in beaker.reagents.reagent_list) - beakerContents.Add(list(list("name" = R.name, "volume" = R.volume, "purity" = R.purity))) // list in a list because Byond merges the first list... + beakerContents.Add(list(list("name" = R.name, "volume" = round(R.volume, 0.01), "purity" = round(R.purity, 0.01)))) // list in a list because Byond merges the first list... data["beakerContents"] = beakerContents return data diff --git a/code/modules/reagents/chemistry/machinery/chem_master.dm b/code/modules/reagents/chemistry/machinery/chem_master.dm index 89fdb52b2a..285ef70433 100644 --- a/code/modules/reagents/chemistry/machinery/chem_master.dm +++ b/code/modules/reagents/chemistry/machinery/chem_master.dm @@ -184,13 +184,13 @@ var/beakerContents[0] if(beaker) for(var/datum/reagent/R in beaker.reagents.reagent_list) - beakerContents.Add(list(list("name" = R.name, "id" = ckey(R.name), "volume" = R.volume))) // list in a list because Byond merges the first list... + beakerContents.Add(list(list("name" = R.name, "id" = R.type, "volume" = R.volume))) // list in a list because Byond merges the first list... data["beakerContents"] = beakerContents var/bufferContents[0] if(reagents.total_volume) for(var/datum/reagent/N in reagents.reagent_list) - bufferContents.Add(list(list("name" = N.name, "id" = ckey(N.name), "volume" = N.volume))) // ^ + bufferContents.Add(list(list("name" = N.name, "id" = N.type, "volume" = N.volume))) // ^ data["bufferContents"] = bufferContents //Calculated at init time as it never changes @@ -216,7 +216,7 @@ if(action == "transfer") if(!beaker) return FALSE - var/reagent = GLOB.name2reagent[params["id"]] + var/reagent = text2path(params["id"]) var/amount = text2num(params["amount"]) var/to_container = params["to"] // Custom amount @@ -386,7 +386,7 @@ if(action == "analyze") // var/datum/reagent/R = GLOB.name2reagent[params["id"]] - var/reagent = GLOB.name2reagent[params["id"]] + var/reagent = text2path(params["id"]) var/datum/reagent/R = GLOB.chemical_reagents_list[reagent] if(R) var/state = "Unknown" @@ -405,7 +405,7 @@ analyzeVars = list("name" = initial(R.name), "state" = state, "color" = initial(R.color), "description" = initial(R.description), "metaRate" = T, "overD" = initial(R.overdose_threshold), "addicD" = initial(R.addiction_threshold), "purityF" = R.purity, "inverseRatioF" = initial(R.inverse_chem_val), "purityE" = initial(Rcr.PurityMin), "minTemp" = initial(Rcr.OptimalTempMin), "maxTemp" = initial(Rcr.OptimalTempMax), "eTemp" = initial(Rcr.ExplodeTemp), "pHpeak" = pHpeakCache) else fermianalyze = FALSE - analyzeVars = list("name" = initial(R.name), "state" = state, "color" = initial(R.color), "description" = initial(R.description), "metaRate" = T, "overD" = initial(R.overdose_threshold), "addicD" = initial(R.addiction_threshold)) + analyzeVars = list("name" = initial(R.name), "state" = state, "color" = initial(R.color), "description" = initial(R.description), "metaRate" = T, "overD" = initial(R.overdose_threshold), "addicD" = initial(R.addiction_threshold), "purityF" = R.purity) screen = "analyze" return TRUE diff --git a/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm b/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm index a0de9f7a7f..aae21464b1 100644 --- a/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm @@ -2288,6 +2288,7 @@ All effects don't start immediately, but rather get worse over time; the rate is //Race-Base-Drinks// //////////////////// /datum/reagent/consumable/ethanol/species_drink + name = "Species Drink" var/species_required var/disgust = 26 boozepwr = 50 diff --git a/code/modules/reagents/chemistry/reagents/drink_reagents.dm b/code/modules/reagents/chemistry/reagents/drink_reagents.dm index 37fc075c6f..f4f5b90398 100644 --- a/code/modules/reagents/chemistry/reagents/drink_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/drink_reagents.dm @@ -504,6 +504,14 @@ glass_desc = "Don't cry, Don't raise your eye, It's only nuclear wasteland." value = REAGENT_VALUE_COMMON +/datum/reagent/consumable/nuka_cola/on_mob_metabolize(mob/living/carbon/M) + M.add_movespeed_modifier(/datum/movespeed_modifier/reagent/meth) + return ..() + +/datum/reagent/consumable/nuka_cola/on_mob_end_metabolize(mob/living/carbon/M) + M.remove_movespeed_modifier(/datum/movespeed_modifier/reagent/meth) + return ..() + /datum/reagent/consumable/nuka_cola/on_mob_life(mob/living/carbon/M) M.Jitter(20) M.set_drugginess(30) @@ -608,7 +616,7 @@ name = "Buzz Fuzz" description = "~A Hive of Flavour!~ NOTICE: Addicting." nutriment_factor = 0 - addiction_threshold = 26 //A can and a sip + addiction_threshold = 31 //A can and a sip color = "#8CFF00" // rgb: 135, 255, 0 taste_description = "carbonated honey and pollen" glass_icon_state = "buzz_fuzz" @@ -625,8 +633,9 @@ myseed.adjust_potency(round(chems.get_reagent_amount(src.type) * 0.5)) /datum/reagent/consumable/buzz_fuzz/on_mob_life(mob/living/carbon/M) - M.reagents.add_reagent(/datum/reagent/consumable/sugar,1) - if(prob(5)) + if(prob(33)) + M.reagents.add_reagent(/datum/reagent/consumable/sugar,1) + if(prob(1)) M.reagents.add_reagent(/datum/reagent/consumable/honey,1) ..() diff --git a/code/modules/reagents/chemistry/reagents/drug_reagents.dm b/code/modules/reagents/chemistry/reagents/drug_reagents.dm index 0b44c33926..96985514b5 100644 --- a/code/modules/reagents/chemistry/reagents/drug_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/drug_reagents.dm @@ -45,6 +45,13 @@ trippy = FALSE pH = 8 +//Nicotine is used as a pesticide IRL. +/datum/reagent/drug/nicotine/on_hydroponics_apply(obj/item/seeds/myseed, datum/reagents/chems, obj/machinery/hydroponics/mytray, mob/user) + . = ..() + if(chems.has_reagent(type, 1)) + mytray.adjustToxic(round(chems.get_reagent_amount(type))) + mytray.adjustPests(-rand(1,2)) + /datum/reagent/drug/nicotine/on_mob_life(mob/living/carbon/M) if(prob(1)) var/smoke_message = pick("You feel relaxed.", "You feel calmed.","You feel alert.","You feel rugged.") @@ -174,11 +181,13 @@ ADD_TRAIT(L, TRAIT_IGNOREDAMAGESLOWDOWN, type) L.update_movespeed() ADD_TRAIT(L, TRAIT_TASED_RESISTANCE, type) + L.add_movespeed_modifier(/datum/movespeed_modifier/reagent/meth) /datum/reagent/drug/methamphetamine/on_mob_end_metabolize(mob/living/L) REMOVE_TRAIT(L, TRAIT_IGNOREDAMAGESLOWDOWN, type) L.update_movespeed() REMOVE_TRAIT(L, TRAIT_TASED_RESISTANCE, type) + L.remove_movespeed_modifier(/datum/movespeed_modifier/reagent/meth) ..() /datum/reagent/drug/methamphetamine/on_mob_life(mob/living/carbon/M) @@ -561,3 +570,129 @@ var/mob/living/carbon/C = M if(!C.undergoing_cardiac_arrest()) C.set_heartattack(TRUE) + +//aphrodisiac & anaphrodisiac + +/datum/reagent/drug/aphrodisiac + name = "Crocin" + description = "Naturally found in the crocus and gardenia flowers, this drug acts as a natural and safe aphrodisiac." + taste_description = "strawberries" + color = "#FFADFF"//PINK, rgb(255, 173, 255) + can_synth = FALSE + +/datum/reagent/drug/aphrodisiac/on_mob_life(mob/living/M) + if(M && M.client?.prefs.arousable && !(M.client?.prefs.cit_toggles & NO_APHRO)) + if((prob(min(current_cycle/2,5)))) + M.emote(pick("moan","blush")) + if(prob(min(current_cycle/4,10))) + var/aroused_message = pick("You feel frisky.", "You're having trouble suppressing your urges.", "You feel in the mood.") + to_chat(M, "[aroused_message]") + if(ishuman(M)) + var/mob/living/carbon/human/H = M + var/list/genits = H.adjust_arousal(current_cycle, "crocin", aphro = TRUE) // redundant but should still be here + for(var/g in genits) + var/obj/item/organ/genital/G = g + to_chat(M, "[G.arousal_verb]!") + ..() + +/datum/reagent/drug/aphrodisiacplus + name = "Hexacrocin" + description = "Chemically condensed form of basic crocin. This aphrodisiac is extremely powerful and addictive in most animals.\ + Addiction withdrawals can cause brain damage and shortness of breath. Overdosage can lead to brain damage and a \ + permanent increase in libido (commonly referred to as 'bimbofication')." + taste_description = "liquid desire" + color = "#FF2BFF"//dark pink + addiction_threshold = 20 + overdose_threshold = 20 + can_synth = FALSE + +/datum/reagent/drug/aphrodisiacplus/on_mob_life(mob/living/M) + if(M && M.client?.prefs.arousable && !(M.client?.prefs.cit_toggles & NO_APHRO)) + if(prob(5)) + if(prob(current_cycle)) + M.say(pick("Hnnnnngghh...", "Ohh...", "Mmnnn...")) + else + M.emote(pick("moan","blush")) + if(prob(5)) + var/aroused_message + if(current_cycle>25) + aroused_message = pick("You need to fuck someone!", "You're bursting with sexual tension!", "You can't get sex off your mind!") + else + aroused_message = pick("You feel a bit hot.", "You feel strong sexual urges.", "You feel in the mood.", "You're ready to go down on someone.") + to_chat(M, "[aroused_message]") + REMOVE_TRAIT(M,TRAIT_NEVERBONER,APHRO_TRAIT) + if(ishuman(M)) + var/mob/living/carbon/human/H = M + var/list/genits = H.adjust_arousal(100, "hexacrocin", aphro = TRUE) // redundant but should still be here + for(var/g in genits) + var/obj/item/organ/genital/G = g + to_chat(M, "[G.arousal_verb]!") + ..() + +/datum/reagent/drug/aphrodisiacplus/addiction_act_stage2(mob/living/M) + if(prob(30)) + M.adjustOrganLoss(ORGAN_SLOT_BRAIN, 2) + ..() +/datum/reagent/drug/aphrodisiacplus/addiction_act_stage3(mob/living/M) + if(prob(30)) + M.adjustOrganLoss(ORGAN_SLOT_BRAIN, 3) + + ..() +/datum/reagent/drug/aphrodisiacplus/addiction_act_stage4(mob/living/M) + if(prob(30)) + M.adjustOrganLoss(ORGAN_SLOT_BRAIN, 4) + ..() + +/datum/reagent/drug/aphrodisiacplus/overdose_process(mob/living/M) + if(M && M.client?.prefs.arousable && !(M.client?.prefs.cit_toggles & NO_APHRO) && prob(33)) + if(prob(5) && ishuman(M) && M.has_dna() && (M.client?.prefs.cit_toggles & BIMBOFICATION)) + if(!HAS_TRAIT(M,TRAIT_PERMABONER)) + to_chat(M, "Your libido is going haywire!") + M.log_message("Made perma-horny by hexacrocin.",LOG_EMOTE) + ADD_TRAIT(M,TRAIT_PERMABONER,APHRO_TRAIT) + ..() + +/datum/reagent/drug/anaphrodisiac + name = "Camphor" + description = "Naturally found in some species of evergreen trees, camphor is a waxy substance. When injested by most animals, it acts as an anaphrodisiac\ + , reducing libido and calming them. Non-habit forming and not addictive." + taste_description = "dull bitterness" + taste_mult = 2 + color = "#D9D9D9"//rgb(217, 217, 217) + reagent_state = SOLID + can_synth = FALSE + +/datum/reagent/drug/anaphrodisiac/on_mob_life(mob/living/M) + if(M && M.client?.prefs.arousable && prob(16)) + if(ishuman(M)) + var/mob/living/carbon/human/H = M + var/list/genits = H.adjust_arousal(-100, "camphor", aphro = TRUE) + if(genits.len) + to_chat(M, "You no longer feel aroused.") + ..() + +/datum/reagent/drug/anaphrodisiacplus + name = "Hexacamphor" + description = "Chemically condensed camphor. Causes an extreme reduction in libido and a permanent one if overdosed. Non-addictive." + taste_description = "tranquil celibacy" + color = "#D9D9D9"//rgb(217, 217, 217) + reagent_state = SOLID + overdose_threshold = 20 + can_synth = FALSE + +/datum/reagent/drug/anaphrodisiacplus/on_mob_life(mob/living/M) + if(M && M.client?.prefs.arousable) + REMOVE_TRAIT(M,TRAIT_PERMABONER,APHRO_TRAIT) + if(ishuman(M)) + var/mob/living/carbon/human/H = M + var/list/genits = H.adjust_arousal(-100, "hexacamphor", aphro = TRUE) + if(genits.len) + to_chat(M, "You no longer feel aroused.") + + ..() + +/datum/reagent/drug/anaphrodisiacplus/overdose_process(mob/living/M) + if(M && M.client?.prefs.arousable && prob(5)) + to_chat(M, "You feel like you'll never feel aroused again...") + ADD_TRAIT(M,TRAIT_NEVERBONER,APHRO_TRAIT) + ..() diff --git a/code/modules/reagents/chemistry/reagents/food_reagents.dm b/code/modules/reagents/chemistry/reagents/food_reagents.dm index 1975eede70..303fd981fb 100644 --- a/code/modules/reagents/chemistry/reagents/food_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/food_reagents.dm @@ -49,6 +49,11 @@ var/brute_heal = 1 var/burn_heal = 0 +/datum/reagent/consumable/nutriment/on_hydroponics_apply(obj/item/seeds/myseed, datum/reagents/chems, obj/machinery/hydroponics/mytray, mob/user) + . = ..() + if(chems.has_reagent(type, 1)) + mytray.adjustHealth(round(chems.get_reagent_amount(type) * 0.2)) + /datum/reagent/consumable/nutriment/on_mob_life(mob/living/carbon/M) if(!HAS_TRAIT(M, TRAIT_NO_PROCESS_FOOD)) if(prob(50)) diff --git a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm index 7ab50884e0..65443b65c7 100644 --- a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm @@ -155,6 +155,12 @@ pH = 11 value = REAGENT_VALUE_COMMON +// Healing +/datum/reagent/medicine/cryoxadone/on_hydroponics_apply(obj/item/seeds/myseed, datum/reagents/chems, obj/machinery/hydroponics/mytray, mob/user) + . = ..() + mytray.adjustHealth(round(chems.get_reagent_amount(type) * 3)) + mytray.adjustToxic(-round(chems.get_reagent_amount(type) * 3)) + /datum/reagent/medicine/cryoxadone/on_mob_life(mob/living/carbon/M) var/power = -0.00003 * (M.bodytemperature ** 2) + 3 if(M.bodytemperature < T0C) @@ -272,10 +278,10 @@ overdose_threshold = 50 /datum/reagent/medicine/silver_sulfadiazine/reaction_obj(obj/O, reac_volume) - if(istype(O, /obj/item/stack/medical/gauze)) + if(istype(O, /obj/item/stack/medical/gauze/adv)) var/obj/item/stack/medical/gauze/G = O - reac_volume = min((reac_volume / 10), G.amount) - new/obj/item/stack/medical/mesh(get_turf(G), reac_volume) + reac_volume = min((reac_volume / 5), G.amount) + new /obj/item/stack/medical/mesh/five(get_turf(G), reac_volume) G.use(reac_volume) /datum/reagent/medicine/silver_sulfadiazine/reaction_mob(mob/living/M, method=TOUCH, reac_volume, show_message = 1) @@ -358,10 +364,10 @@ ..() /datum/reagent/medicine/styptic_powder/reaction_obj(obj/O, reac_volume) - if(istype(O, /obj/item/stack/medical/gauze)) + if(istype(O, /obj/item/stack/medical/gauze/adv)) var/obj/item/stack/medical/gauze/G = O - reac_volume = min((reac_volume / 10), G.amount) - new/obj/item/stack/medical/suture(get_turf(G), reac_volume) + reac_volume = min((reac_volume / 5), G.amount) + new /obj/item/stack/medical/suture/five(get_turf(G), reac_volume) G.use(reac_volume) /datum/reagent/medicine/styptic_powder/on_mob_life(mob/living/carbon/M) @@ -499,7 +505,7 @@ SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "painful_medicine", /datum/mood_event/painful_medicine) var/vol = reac_volume + M.reagents.get_reagent_amount(/datum/reagent/medicine/synthflesh) //Has to be at less than THRESHOLD_UNHUSK burn damage and have 100 synthflesh before unhusking. Corpses dont metabolize. - if(HAS_TRAIT_FROM(M, TRAIT_HUSK, "burn") && M.getFireLoss() < THRESHOLD_UNHUSK && (vol > 100)) + if(HAS_TRAIT_FROM(M, TRAIT_HUSK, "burn") && M.getFireLoss() < THRESHOLD_UNHUSK && (vol >= 100)) M.cure_husk("burn") M.visible_message("Most of [M]'s burnt off or charred flesh has been restored.") ..() @@ -935,6 +941,12 @@ pH = 0 value = REAGENT_VALUE_RARE +// FEED ME SEYMOUR +/datum/reagent/medicine/strange_reagent/on_hydroponics_apply(obj/item/seeds/myseed, datum/reagents/chems, obj/machinery/hydroponics/mytray, mob/user) + . = ..() + if(chems.has_reagent(type, 1)) + mytray.spawnplant() + /datum/reagent/medicine/strange_reagent/reaction_mob(mob/living/M, method=TOUCH, reac_volume) if(M.stat == DEAD) if(M.suiciding || M.hellbound) //they are never coming back diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm index 193b96e841..fe8f923e1a 100644 --- a/code/modules/reagents/chemistry/reagents/other_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm @@ -1125,7 +1125,7 @@ /datum/reagent/space_cleaner/sterilizine/reaction_obj(obj/O, reac_volume) if(istype(O, /obj/item/stack/medical/gauze)) var/obj/item/stack/medical/gauze/G = O - reac_volume = min((reac_volume / 10), G.amount) + reac_volume = min((reac_volume / 5), G.amount) new /obj/item/stack/medical/gauze/adv(get_turf(G), reac_volume) G.use(reac_volume) @@ -2362,6 +2362,12 @@ M.emote("nya") if(prob(20)) to_chat(M, "[pick("Headpats feel nice.", "The feeling of a hairball...", "Backrubs would be nice.", "Whats behind those doors?")]") + if(ishuman(M)) + var/mob/living/carbon/human/H = M + var/list/adjusted = H.adjust_arousal(2,"catnip", aphro = TRUE) + for(var/g in adjusted) + var/obj/item/organ/genital/G = g + to_chat(M, "You feel like playing with your [G.name]!") ..() /datum/reagent/preservahyde @@ -2528,7 +2534,7 @@ M.adjustStaminaLoss(-0.25*REM) // the more wounds, the more stamina regen ..() -datum/reagent/eldritch +/datum/reagent/eldritch name = "Eldritch Essence" description = "Strange liquid that defies the laws of physics" taste_description = "Ag'hsj'saje'sh" @@ -2539,7 +2545,7 @@ datum/reagent/eldritch M.drowsyness = max(M.drowsyness-5, 0) M.AdjustAllImmobility(-40, FALSE) M.adjustStaminaLoss(-15, FALSE) - M.adjustToxLoss(-3, FALSE) + M.adjustToxLoss(-3, FALSE, TRUE) M.adjustOxyLoss(-3, FALSE) M.adjustBruteLoss(-3, FALSE) M.adjustFireLoss(-3, FALSE) @@ -2663,3 +2669,32 @@ datum/reagent/eldritch M.SetSleeping(0, 0) ..() +//Nerdy card reagents + +/datum/reagent/card_powder + var/rarity = "Stoopid" + +/datum/reagent/card_powder/blue + name = "Blue Card Powder" + rarity = "Rare" + color = "#00B7EF" // blue + +/datum/reagent/card_powder/purple + name = "Purple Card Powder" + rarity = "Epic" + color = "#DA00FF" // purple + +/datum/reagent/card_powder/yellow + name = "Yellow Crayon Powder" + rarity = "Legendary" + color = "#FFF200" // yellow + +/datum/reagent/card_powder/green + name = "Green Crayon Powder" + rarity = "Common" + color = "#A8E61D" // green + +/datum/reagent/card_powder/black + name = "Black Crayon Powder" + rarity = "Exodia" + color = "#1C1C1C" // not quite black diff --git a/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm b/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm index 3f0ebcb3e3..2e05f66cf8 100644 --- a/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm @@ -32,6 +32,12 @@ value = REAGENT_VALUE_VERY_COMMON taste_description = "metal" +//It has stable IN THE NAME. IT WAS MADE FOR THIS MOMENT. +/datum/reagent/stabilizing_agent/on_hydroponics_apply(obj/item/seeds/myseed, datum/reagents/chems, obj/machinery/hydroponics/mytray, mob/user) + . = ..() + if(myseed && chems.has_reagent(type, 1)) + myseed.adjust_instability(-1) + /datum/reagent/clf3 name = "Chlorine Trifluoride" description = "Makes a temporary 3x3 fireball when it comes into existence, so be careful when mixing. ClF3 applied to a surface burns things that wouldn't otherwise burn, sometimes through the very floors of the station and exposing it to the vacuum of space." @@ -167,6 +173,15 @@ taste_description = "burning" value = REAGENT_VALUE_COMMON +// Smells like victory... +/datum/reagent/napalm/on_hydroponics_apply(obj/item/seeds/myseed, datum/reagents/chems, obj/machinery/hydroponics/mytray, mob/user) + . = ..() + if(chems.has_reagent(type, 1)) + if(!(myseed.resistance_flags & FIRE_PROOF)) + mytray.adjustHealth(-round(chems.get_reagent_amount(type) * 6)) + mytray.adjustToxic(round(chems.get_reagent_amount(type) * 7)) + mytray.adjustWeeds(-rand(5,9)) //At least give them a small reward if they bother. + /datum/reagent/napalm/on_mob_life(mob/living/carbon/M) M.adjust_fire_stacks(1) ..() diff --git a/code/modules/reagents/chemistry/reagents/toxin_reagents.dm b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm index 0d0a234afc..c9fbf6928a 100644 --- a/code/modules/reagents/chemistry/reagents/toxin_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm @@ -783,6 +783,8 @@ value = REAGENT_VALUE_VERY_RARE /datum/reagent/toxin/rotatium/on_mob_life(mob/living/carbon/M) + return ..() // until fixed - the rotations never stop +/* if(M.hud_used) if(current_cycle >= 20 && current_cycle%20 == 0) var/list/screens = list(M.hud_used.plane_masters["[FLOOR_PLANE]"], M.hud_used.plane_masters["[GAME_PLANE]"], @@ -800,6 +802,7 @@ for(var/whole_screen in screens) animate(whole_screen, transform = matrix(), time = 5, easing = QUAD_EASING) ..() +*/ /datum/reagent/toxin/skewium name = "Skewium" diff --git a/code/modules/reagents/chemistry/recipes/drugs.dm b/code/modules/reagents/chemistry/recipes/drugs.dm index 468d29c052..cd55d00cf1 100644 --- a/code/modules/reagents/chemistry/recipes/drugs.dm +++ b/code/modules/reagents/chemistry/recipes/drugs.dm @@ -62,3 +62,35 @@ results = list(/datum/reagent/moonsugar = 1, /datum/reagent/medicine/morphine = 2.5) required_temp = 315 //a little above normal body temperature required_reagents = list(/datum/reagent/drug/skooma = 1) + +/datum/chemical_reaction/aphro + name = "crocin" + id = /datum/reagent/drug/aphrodisiac + results = list(/datum/reagent/drug/aphrodisiac = 6) + required_reagents = list(/datum/reagent/carbon = 2, /datum/reagent/hydrogen = 2, /datum/reagent/oxygen = 2, /datum/reagent/water = 1) + required_temp = 400 + mix_message = "The mixture boils off a pink vapor..."//The water boils off, leaving the crocin + +/datum/chemical_reaction/aphroplus + name = "hexacrocin" + id = /datum/reagent/drug/aphrodisiacplus + results = list(/datum/reagent/drug/aphrodisiacplus = 1) + required_reagents = list(/datum/reagent/drug/aphrodisiac = 6, /datum/reagent/phenol = 1) + required_temp = 400 + mix_message = "The mixture rapidly condenses and darkens in color..." + +/datum/chemical_reaction/anaphro + name = "camphor" + id = /datum/reagent/drug/anaphrodisiac + results = list(/datum/reagent/drug/anaphrodisiac = 6) + required_reagents = list(/datum/reagent/carbon = 2, /datum/reagent/hydrogen = 2, /datum/reagent/oxygen = 2, /datum/reagent/sulfur = 1) + required_temp = 400 + mix_message = "The mixture boils off a yellow, smelly vapor..."//Sulfur burns off, leaving the camphor + +/datum/chemical_reaction/anaphroplus + name = "hexacamphor" + id = /datum/reagent/drug/anaphrodisiacplus + results = list(/datum/reagent/drug/anaphrodisiacplus = 1) + required_reagents = list(/datum/reagent/drug/anaphrodisiac = 6, /datum/reagent/acetone = 1) + required_temp = 400 + mix_message = "The mixture thickens and heats up slighty..." diff --git a/code/modules/reagents/chemistry/recipes/medicine.dm b/code/modules/reagents/chemistry/recipes/medicine.dm index d4d66af240..f2e9bd9e1a 100644 --- a/code/modules/reagents/chemistry/recipes/medicine.dm +++ b/code/modules/reagents/chemistry/recipes/medicine.dm @@ -116,7 +116,10 @@ holder.remove_reagent(id, added_volume*temp_ratio) if(St.purity < 1) St.volume *= St.purity + added_volume *= St.purity St.purity = 1 + if(!N) + return var/amount = clamp(0.002, 0, N.volume) N.volume -= amount St.data["grown_volume"] = St.data["grown_volume"] + added_volume diff --git a/code/modules/reagents/chemistry/recipes/others.dm b/code/modules/reagents/chemistry/recipes/others.dm index 019394e3c8..6eb0d5825c 100644 --- a/code/modules/reagents/chemistry/recipes/others.dm +++ b/code/modules/reagents/chemistry/recipes/others.dm @@ -718,7 +718,7 @@ name = "felinid mutation toxin" id = /datum/reagent/mutationtoxin/felinid results = list(/datum/reagent/mutationtoxin/felinid = 1) - required_reagents = list(/datum/reagent/toxin/mindbreaker = 1, /datum/reagent/ammonia = 1, /datum/reagent/water = 1, /datum/reagent/pax/catnip = 1, /datum/reagent/mutationtoxin = 1) + required_reagents = list(/datum/reagent/toxin/mindbreaker = 1, /datum/reagent/ammonia = 1, /datum/reagent/water = 1, /datum/reagent/drug/aphrodisiac = 10, /datum/reagent/mutationtoxin = 1) required_temp = 450 /datum/chemical_reaction/moff @@ -881,3 +881,29 @@ results = list(/datum/reagent/carbon = 1) required_reagents = list(/datum/reagent/cellulose = 1) required_temp = 512 + +//Nerdy card shit + +/datum/chemical_reaction/card_powder/blue + name = "Blue Card Powder" + id = /datum/reagent/card_powder/blue + results = list(/datum/reagent/card_powder/blue = 1) + required_reagents = list(/datum/reagent/card_powder/green = 12) + +/datum/chemical_reaction/card_powder/purple + name = "Purple Card Powder" + id = /datum/reagent/card_powder/purple + results = list(/datum/reagent/card_powder/purple = 1) + required_reagents = list(/datum/reagent/card_powder/blue = 12) + +/datum/chemical_reaction/card_powder/yellow + name = "Yellow Card Powder" + id = /datum/reagent/card_powder/yellow + results = list(/datum/reagent/card_powder/yellow = 1) + required_reagents = list(/datum/reagent/card_powder/purple = 12) + +/datum/chemical_reaction/card_powder/black + name = "Black Card Powder" + id = /datum/reagent/card_powder/black + results = list(/datum/reagent/card_powder/black = 1) + required_reagents = list(/datum/reagent/card_powder/yellow = 12) diff --git a/code/modules/reagents/reagent_containers/blood_pack.dm b/code/modules/reagents/reagent_containers/blood_pack.dm index 98a117ea69..acfda85963 100644 --- a/code/modules/reagents/reagent_containers/blood_pack.dm +++ b/code/modules/reagents/reagent_containers/blood_pack.dm @@ -104,31 +104,44 @@ return ..() /obj/item/reagent_containers/blood/attack(mob/living/carbon/C, mob/user, def_zone) - if(user.a_intent == INTENT_HELP && reagents.total_volume > 0 && iscarbon(C) && user.a_intent == INTENT_HELP) - if(C.is_mouth_covered()) - to_chat(user, "You cant drink from the [src] while your mouth is covered.") - return - if(user != C) - user.visible_message("[user] forces [C] to drink from the [src].", \ - "You force [C] to drink from the [src]") - if(!do_mob(user, C, 50)) - return - else - if(!do_mob(user, C, 10)) - return + if(!iscarbon(C) || user.a_intent != INTENT_HELP || reagents.total_volume <= 0) + ..() - to_chat(user, "You take a sip from the [src].") - user.visible_message("[user] puts the [src] up to their mouth.") - if(reagents.total_volume <= 0) // Safety: In case you spam clicked the blood bag on yourself, and it is now empty (below will divide by zero) + if(C.is_mouth_covered()) + if(user != C) + to_chat(user, "You can't force [C] to drink from [src] while their mouth is covered.") return - var/gulp_size = 3 - var/fraction = min(gulp_size / reagents.total_volume, 1) - reagents.reaction(C, INGEST, fraction) //checkLiked(fraction, M) // Blood isn't food, sorry. - reagents.trans_to(C, gulp_size) - reagents.remove_reagent(src, 2) //Inneficency, so hey, IVs are usefull. - playsound(C.loc,'sound/items/drink.ogg', rand(10, 50), TRUE) + to_chat(user, "You can't drink from [src] while your mouth is covered.") return - ..() + + if(!user.CheckActionCooldown()) + return + if(user != C) + user.visible_message("[user] forces [C] to drink from [src].", \ + "You force [C] to drink from [src]") + user.DelayNextAction(50) + if(do_mob(user, C, 50)) + do_drink(C, user) + + else + user.DelayNextAction(10) + if(do_mob(user, C, 10)) + user.visible_message("[user] puts [src] up to their mouth.", \ + "You take a sip from [src].") + do_drink(C, user) + + +/obj/item/reagent_containers/blood/proc/do_drink(mob/living/carbon/C, mob/user) + if(reagents.total_volume <= 0) // Safety: In case you spam clicked the blood bag on yourself, and it is now empty (below will divide by zero) + to_chat(user, "...and notice [src] is empty.") + return + var/gulp_size = 3 + var/fraction = min(gulp_size / reagents.total_volume, 1) + reagents.reaction(C, INGEST, fraction) //checkLiked(fraction, M) // Blood isn't food, sorry. + reagents.remove_any(5) //Inneficency, so hey, IVs are usefull. + reagents.trans_to(C, gulp_size) + playsound(C.loc,'sound/items/drink.ogg', rand(10, 50), TRUE) + /obj/item/reagent_containers/blood/bluespace name = "bluespace blood pack" diff --git a/code/modules/reagents/reagent_containers/bottle.dm b/code/modules/reagents/reagent_containers/bottle.dm index e0a7f7c00e..76b08c7cdf 100644 --- a/code/modules/reagents/reagent_containers/bottle.dm +++ b/code/modules/reagents/reagent_containers/bottle.dm @@ -417,6 +417,26 @@ name = "bromine bottle" list_reagents = list(/datum/reagent/bromine = 30) +/obj/item/reagent_containers/glass/bottle/crocin + name = "Crocin bottle" + desc = "A bottle of mild aphrodisiac. Increases libido." + list_reagents = list(/datum/reagent/drug/aphrodisiac = 30) + +/obj/item/reagent_containers/glass/bottle/hexacrocin + name = "Hexacrocin bottle" + desc = "A bottle of strong aphrodisiac. Increases libido." + list_reagents = list(/datum/reagent/drug/aphrodisiacplus = 30) + +/obj/item/reagent_containers/glass/bottle/camphor + name = "Camphor bottle" + desc = "A bottle of mild anaphrodisiac. Reduces libido." + list_reagents = list(/datum/reagent/drug/anaphrodisiac = 30) + +/obj/item/reagent_containers/glass/bottle/hexacamphor + name = "Hexacamphor bottle" + desc = "A bottle of strong anaphrodisiac. Reduces libido." + list_reagents = list(/datum/reagent/drug/anaphrodisiacplus = 30) + //Ichors /obj/item/reagent_containers/glass/bottle/ichor possible_transfer_amounts = list(1) diff --git a/code/modules/reagents/reagent_containers/chem_pack.dm b/code/modules/reagents/reagent_containers/chem_pack.dm index 78ae1a3070..77d6067d3b 100644 --- a/code/modules/reagents/reagent_containers/chem_pack.dm +++ b/code/modules/reagents/reagent_containers/chem_pack.dm @@ -44,8 +44,7 @@ else . += "Alt-click to seal it." - -obj/item/reagent_containers/chem_pack/attack_self(mob/user) +/obj/item/reagent_containers/chem_pack/attack_self(mob/user) if(sealed) return ..() diff --git a/code/modules/reagents/reagent_containers/dropper.dm b/code/modules/reagents/reagent_containers/dropper.dm index 3ed946d0c5..bad9417dc8 100644 --- a/code/modules/reagents/reagent_containers/dropper.dm +++ b/code/modules/reagents/reagent_containers/dropper.dm @@ -48,7 +48,7 @@ safe_thing.create_reagents(100, NONE, NO_REAGENTS_VALUE) reagents.reaction(safe_thing, TOUCH, fraction) - trans = reagents.trans_to(safe_thing, amount_per_transfer_from_this) + trans = reagents.trans_to(safe_thing, amount_per_transfer_from_this, log = "failed squirt") target.visible_message("[user] tries to squirt something into [target]'s eyes, but fails!", \ "[user] tries to squirt something into [target]'s eyes, but fails!") @@ -67,7 +67,7 @@ var/mob/M = target log_combat(user, M, "squirted", reagents.log_list()) - trans = src.reagents.trans_to(target, amount_per_transfer_from_this) + trans = src.reagents.trans_to(target, amount_per_transfer_from_this, log = "dropper drop") to_chat(user, "You transfer [trans] unit\s of the solution.") update_icon() @@ -81,7 +81,7 @@ to_chat(user, "[target] is empty!") return - var/trans = target.reagents.trans_to(src, amount_per_transfer_from_this) + var/trans = target.reagents.trans_to(src, amount_per_transfer_from_this, log = "dropper fill") to_chat(user, "You fill [src] with [trans] unit\s of the solution.") diff --git a/code/modules/reagents/reagent_containers/hypospray.dm b/code/modules/reagents/reagent_containers/hypospray.dm index 1f89af420e..143025aed1 100644 --- a/code/modules/reagents/reagent_containers/hypospray.dm +++ b/code/modules/reagents/reagent_containers/hypospray.dm @@ -41,7 +41,7 @@ if(M.reagents) var/trans = 0 if(!infinite) - trans = reagents.trans_to(M, amount_per_transfer_from_this, log = TRUE) + trans = reagents.trans_to(M, amount_per_transfer_from_this, log = "hypospray injection") else trans = reagents.copy_to(M, amount_per_transfer_from_this) @@ -427,7 +427,7 @@ var/fraction = min(vial.amount_per_transfer_from_this/vial.reagents.total_volume, 1) vial.reagents.reaction(L, method, fraction) - vial.reagents.trans_to(target, vial.amount_per_transfer_from_this, log = TRUE) + vial.reagents.trans_to(target, vial.amount_per_transfer_from_this, log = "hypospray fill") var/long_sound = vial.amount_per_transfer_from_this >= 15 playsound(loc, long_sound ? 'sound/items/hypospray_long.ogg' : pick('sound/items/hypospray.ogg','sound/items/hypospray2.ogg'), 50, 1, -1) to_chat(user, "You [fp_verb] [vial.amount_per_transfer_from_this] units of the solution. The hypospray's cartridge now contains [vial.reagents.total_volume] units.") diff --git a/code/modules/reagents/reagent_containers/rags.dm b/code/modules/reagents/reagent_containers/rags.dm index 5d94e78809..8a6e2bf2e7 100644 --- a/code/modules/reagents/reagent_containers/rags.dm +++ b/code/modules/reagents/reagent_containers/rags.dm @@ -39,7 +39,7 @@ C.visible_message("[user] is trying to smother \the [C] with \the [src]!", "[user] is trying to smother you with \the [src]!", "You hear some struggling and muffled cries of surprise.") if(do_after(user, 20, target = C)) reagents.reaction(C, INGEST) - reagents.trans_to(C, 5, log = TRUE) + reagents.trans_to(C, 5, log = "rag smother") C.visible_message("[user] has smothered \the [C] with \the [src]!", "[user] has smothered you with \the [src]!", "You hear some struggling and a heavy breath taken.") log_combat(user, C, "smothered", log_object) else @@ -107,7 +107,7 @@ reagents.clear_reagents() else msg += "'s liquids into \the [target]" - reagents.trans_to(target, reagents.total_volume, log = TRUE) + reagents.trans_to(target, reagents.total_volume, log = "rag squeeze dry") to_chat(user, "[msg].") return TRUE diff --git a/code/modules/reagents/reagent_dispenser.dm b/code/modules/reagents/reagent_dispenser.dm index 39ccdd23a2..26f0ca66ee 100644 --- a/code/modules/reagents/reagent_dispenser.dm +++ b/code/modules/reagents/reagent_dispenser.dm @@ -173,7 +173,7 @@ boom() /obj/structure/reagent_dispensers/fueltank/attackby(obj/item/I, mob/living/user, params) - if(istype(I, /obj/item/weldingtool)) + if(I.tool_behaviour == TOOL_WELDER) if(!reagents.has_reagent(/datum/reagent/fuel)) to_chat(user, "[src] is out of fuel!") return diff --git a/code/modules/recycling/conveyor2.dm b/code/modules/recycling/conveyor2.dm index b81c3b88f8..aefb670dd3 100644 --- a/code/modules/recycling/conveyor2.dm +++ b/code/modules/recycling/conveyor2.dm @@ -145,7 +145,7 @@ GLOBAL_LIST_EMPTY(conveyors_by_id) // attack with item, place item on conveyor /obj/machinery/conveyor/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/crowbar)) + if(I.tool_behaviour == TOOL_CROWBAR) user.visible_message("[user] struggles to pry up \the [src] with \the [I].", \ "You struggle to pry up \the [src] with \the [I].") if(I.use_tool(src, user, 40, volume=40)) @@ -155,14 +155,14 @@ GLOBAL_LIST_EMPTY(conveyors_by_id) to_chat(user, "You remove the conveyor belt.") qdel(src) - else if(istype(I, /obj/item/wrench)) + else if(I.tool_behaviour == TOOL_WRENCH) if(!(stat & BROKEN)) I.play_tool_sound(src) setDir(turn(dir,-45)) update_move_direction() to_chat(user, "You rotate [src].") - else if(istype(I, /obj/item/screwdriver)) + else if(I.tool_behaviour == TOOL_SCREWDRIVER) if(!(stat & BROKEN)) verted = verted * -1 update_move_direction() @@ -306,7 +306,7 @@ GLOBAL_LIST_EMPTY(conveyors_by_id) CHECK_TICK /obj/machinery/conveyor_switch/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/crowbar)) + if(I.tool_behaviour == TOOL_CROWBAR) var/obj/item/conveyor_switch_construct/C = new/obj/item/conveyor_switch_construct(src.loc) C.id = id transfer_fingerprints_to(C) diff --git a/code/modules/recycling/disposal/bin.dm b/code/modules/recycling/disposal/bin.dm index b106005a15..ef3a053027 100644 --- a/code/modules/recycling/disposal/bin.dm +++ b/code/modules/recycling/disposal/bin.dm @@ -141,7 +141,7 @@ update_icon() /obj/machinery/disposal/proc/can_stuff_mob_in(mob/living/target, mob/living/user, pushing = FALSE) - if(!pushing && !iscarbon(user) && !user.ventcrawler) //only carbon and ventcrawlers can climb into disposal by themselves. + if(!pushing && !iscarbon(user) && !(SEND_SIGNAL(user, COMSIG_CHECK_VENTCRAWL))) //only carbon and ventcrawlers can climb into disposal by themselves. if(iscyborg(user)) var/mob/living/silicon/robot/borg = user if (!borg.module || !borg.module.canDispose) diff --git a/code/modules/recycling/disposal/construction.dm b/code/modules/recycling/disposal/construction.dm index 348e687e03..c6d015df34 100644 --- a/code/modules/recycling/disposal/construction.dm +++ b/code/modules/recycling/disposal/construction.dm @@ -14,12 +14,12 @@ var/obj/pipe_type = /obj/structure/disposalpipe/segment var/pipename -/obj/structure/disposalconstruct/Initialize(loc, _pipe_type, _dir = SOUTH, flip = FALSE, obj/make_from) +/obj/structure/disposalconstruct/Initialize(mapload, _pipe_type, _dir = SOUTH, flip = FALSE, obj/make_from) . = ..() if(make_from) pipe_type = make_from.type setDir(make_from.dir) - anchored = TRUE + set_anchored(TRUE) else if(_pipe_type) @@ -34,6 +34,8 @@ update_icon() + // AddElement(/datum/element/undertile, TRAIT_T_RAY_VISIBLE) + /obj/structure/disposalconstruct/Move() var/old_dir = dir ..() diff --git a/code/modules/recycling/disposal/holder.dm b/code/modules/recycling/disposal/holder.dm index 2e36a9deaa..f739057699 100644 --- a/code/modules/recycling/disposal/holder.dm +++ b/code/modules/recycling/disposal/holder.dm @@ -73,14 +73,16 @@ /obj/structure/disposalholder/proc/move() set waitfor = FALSE var/obj/structure/disposalpipe/last + var/ticks = 1 while(active) var/obj/structure/disposalpipe/curr = loc last = curr + set_glide_size(DELAY_TO_GLIDE_SIZE(ticks * world.tick_lag)) curr = curr.transfer(src) if(!curr && active) last.expel(src, loc, dir) - stoplag() + ticks = stoplag() if(!(count--)) active = FALSE diff --git a/code/modules/research/designs/computer_part_designs.dm b/code/modules/research/designs/computer_part_designs.dm index db37b13e87..d102a7516b 100644 --- a/code/modules/research/designs/computer_part_designs.dm +++ b/code/modules/research/designs/computer_part_designs.dm @@ -148,7 +148,7 @@ id = "APClink" build_type = PROTOLATHE materials = list(/datum/material/iron = 2000) - build_path = /obj/item/computer_hardware/recharger/APC + build_path = /obj/item/computer_hardware/recharger/apc_recharger category = list("Computer Parts") departmental_flags = DEPARTMENTAL_FLAG_SCIENCE | DEPARTMENTAL_FLAG_ENGINEERING diff --git a/code/modules/research/designs/machine_desings/machine_designs_engi.dm b/code/modules/research/designs/machine_desings/machine_designs_engi.dm index 8908241b83..ca522d2ce1 100644 --- a/code/modules/research/designs/machine_desings/machine_designs_engi.dm +++ b/code/modules/research/designs/machine_desings/machine_designs_engi.dm @@ -111,4 +111,4 @@ id = "spaceship_navigation_beacon" build_path = /obj/item/circuitboard/machine/spaceship_navigation_beacon category = list ("Teleportation Machinery") - departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING | DEPARTMENTAL_FLAG_SCIENCE \ No newline at end of file + departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING | DEPARTMENTAL_FLAG_SCIENCE diff --git a/code/modules/research/designs/weapon_designs.dm b/code/modules/research/designs/weapon_designs.dm index 8c934d39a9..11dd29a416 100644 --- a/code/modules/research/designs/weapon_designs.dm +++ b/code/modules/research/designs/weapon_designs.dm @@ -324,8 +324,7 @@ desc = "A tool that discharges controlled radiation which induces mutation in plant cells. Harmless to other organic life." id = "flora_gun" build_type = PROTOLATHE - materials = list(/datum/material/iron = 2000, /datum/material/glass = 500) - reagents_list = list(/datum/reagent/radium = 20) + materials = list(/datum/material/iron = 2000, /datum/material/glass = 500, /datum/material/uranium = 2000) build_path = /obj/item/gun/energy/floragun category = list("Weapons") departmental_flags = DEPARTMENTAL_FLAG_SERVICE | DEPARTMENTAL_FLAG_SCIENCE diff --git a/code/modules/research/machinery/_production.dm b/code/modules/research/machinery/_production.dm index 5a50120833..319b4a2199 100644 --- a/code/modules/research/machinery/_production.dm +++ b/code/modules/research/machinery/_production.dm @@ -3,7 +3,6 @@ desc = "Makes researched and prototype items with materials and energy." layer = BELOW_OBJ_LAYER var/consoleless_interface = TRUE //Whether it can be used without a console. - var/offstation_security_levels = TRUE var/print_cost_coeff = 1 //Materials needed * coeff = actual. var/list/categories = list() var/datum/component/remote_materials/materials @@ -19,7 +18,11 @@ var/screen = RESEARCH_FABRICATOR_SCREEN_MAIN var/selected_category + var/offstation_security_levels + /obj/machinery/rnd/production/Initialize(mapload) + if(mapload && offstation_security_levels) + log_mapping("Depricated var named \"offstation_security_levels\" at ([x], [y], [z])!") . = ..() create_reagents(0, OPENCONTAINER) matching_designs = list() @@ -27,7 +30,7 @@ stored_research = new host_research = SSresearch.science_tech update_research() - materials = AddComponent(/datum/component/remote_materials, "lathe", mapload) + materials = AddComponent(/datum/component/remote_materials, "lathe", mapload, _after_insert=CALLBACK(src, .proc/AfterMaterialInsert)) RefreshParts() /obj/machinery/rnd/production/Destroy() diff --git a/code/modules/research/machinery/circuit_imprinter.dm b/code/modules/research/machinery/circuit_imprinter.dm index b80e96a1e9..a8ce7305a3 100644 --- a/code/modules/research/machinery/circuit_imprinter.dm +++ b/code/modules/research/machinery/circuit_imprinter.dm @@ -23,11 +23,5 @@ linked_console.linked_imprinter = null ..() -/obj/machinery/rnd/production/circuit_imprinter/calculate_efficiency() - . = ..() - var/obj/item/circuitboard/machine/circuit_imprinter/C = circuit - offstation_security_levels = C.offstation_security_levels - -/obj/machinery/rnd/production/circuit_imprinter/offstation - offstation_security_levels = FALSE - circuit = /obj/item/circuitboard/machine/circuit_imprinter/offstation +/obj/machinery/rnd/production/circuit_imprinter/AfterMaterialInsert() //doesnt use have an animation like lathes do + return diff --git a/code/modules/research/machinery/protolathe.dm b/code/modules/research/machinery/protolathe.dm index b1b31a279c..684f27ccad 100644 --- a/code/modules/research/machinery/protolathe.dm +++ b/code/modules/research/machinery/protolathe.dm @@ -23,12 +23,3 @@ /obj/machinery/rnd/production/protolathe/disconnect_console() linked_console.linked_lathe = null ..() - -/obj/machinery/rnd/production/protolathe/calculate_efficiency() - . = ..() - var/obj/item/circuitboard/machine/protolathe/C = circuit - offstation_security_levels = C.offstation_security_levels - -/obj/machinery/rnd/production/protolathe/offstation - offstation_security_levels = FALSE - circuit = /obj/item/circuitboard/machine/protolathe/offstation diff --git a/code/modules/research/rdconsole.dm b/code/modules/research/rdconsole.dm index a7b266fc0e..a9284a19f7 100644 --- a/code/modules/research/rdconsole.dm +++ b/code/modules/research/rdconsole.dm @@ -194,7 +194,9 @@ Nothing else in the console has ID requirements. locked = FALSE return TRUE -/obj/machinery/computer/rdconsole/multitool_act(mob/user, obj/item/multitool/I) +/obj/machinery/computer/rdconsole/multitool_act(mob/user, obj/item/I) + if(!I.tool_behaviour == TOOL_MULTITOOL) + return var/lathe = linked_lathe && linked_lathe.multitool_act(user, I) var/print = linked_imprinter && linked_imprinter.multitool_act(user, I) return lathe || print diff --git a/code/modules/research/rdmachines.dm b/code/modules/research/rdmachines.dm index 2237284a64..f39f5b1584 100644 --- a/code/modules/research/rdmachines.dm +++ b/code/modules/research/rdmachines.dm @@ -94,13 +94,13 @@ ..() /obj/machinery/rnd/proc/AfterMaterialInsert(item_inserted, id_inserted, amount_inserted) - var/stack_name + var/mat_name if(istype(item_inserted, /obj/item/stack/ore/bluespace_crystal)) - stack_name = "bluespace" + mat_name = "bluespace" use_power(MINERAL_MATERIAL_AMOUNT / 10) else - var/obj/item/stack/S = item_inserted - stack_name = S.name + var/datum/material/M = id_inserted + mat_name = M.name use_power(min(1000, (amount_inserted / 100))) - add_overlay("protolathe_[stack_name]") - addtimer(CALLBACK(src, /atom/proc/cut_overlay, "protolathe_[stack_name]"), 10) + add_overlay("protolathe_[mat_name]") + addtimer(CALLBACK(src, /atom/proc/cut_overlay, "protolathe_[mat_name]"), 10) diff --git a/code/modules/research/server.dm b/code/modules/research/server.dm index 6f8ea50f05..657d3b2e12 100644 --- a/code/modules/research/server.dm +++ b/code/modules/research/server.dm @@ -3,6 +3,9 @@ desc = "A computer system running a deep neural network that processes arbitrary information to produce data useable in the development of new technologies. In layman's terms, it makes research points." icon = 'icons/obj/machines/research.dmi' icon_state = "server" + req_access = list(ACCESS_RD) //ONLY THE R&D CAN CHANGE SERVER SETTINGS. + circuit = /obj/item/circuitboard/machine/rdserver + var/datum/techweb/stored_research var/heat_health = 100 //Code for point mining here. @@ -15,14 +18,11 @@ var/temp_tolerance_low = 0 var/temp_tolerance_high = T20C var/temp_penalty_coefficient = 0.5 //1 = -1 points per degree above high tolerance. 0.5 = -0.5 points per degree above high tolerance. - req_access = list(ACCESS_RD) //ONLY THE R&D CAN CHANGE SERVER SETTINGS. /obj/machinery/rnd/server/Initialize() . = ..() SSresearch.servers |= src stored_research = SSresearch.science_tech - var/obj/item/circuitboard/machine/B = new /obj/item/circuitboard/machine/rdserver(null) - B.apply_default_parts(src) /obj/machinery/rnd/server/Destroy() SSresearch.servers -= src diff --git a/code/modules/research/stock_parts.dm b/code/modules/research/stock_parts.dm index fb29399064..de0ebcb63a 100644 --- a/code/modules/research/stock_parts.dm +++ b/code/modules/research/stock_parts.dm @@ -43,7 +43,7 @@ If you create T5+ please take a pass at gene_modder.dm [L40]. Max_values MUST fi /obj/item/storage/part_replacer/bluespace name = "bluespace rapid part exchange device" - desc = "A version of the RPED that allows for replacement of parts and scanning from a distance, along with higher capacity for parts. Definitely not just a BSRPED painted orange." + desc = "A version of the RPED that allows for replacement of parts and scanning from a distance, along with higher capacity for parts." icon_state = "BS_RPED" w_class = WEIGHT_CLASS_NORMAL works_from_distance = TRUE @@ -109,6 +109,7 @@ If you create T5+ please take a pass at gene_modder.dm [L40]. Max_values MUST fi icon_state = "borgrped" /obj/item/storage/part_replacer/bluespace/cyborg + desc = "A version of the RPED that allows for replacement of parts and scanning from a distance, along with higher capacity for parts. Definitely not just a BSRPED painted orange." icon_state = "borg_BS_RPED" /proc/cmp_rped_sort(obj/item/A, obj/item/B) diff --git a/code/modules/research/xenobiology/crossbreeding/_status_effects.dm b/code/modules/research/xenobiology/crossbreeding/_status_effects.dm index 9b19516f26..f3e14993ed 100644 --- a/code/modules/research/xenobiology/crossbreeding/_status_effects.dm +++ b/code/modules/research/xenobiology/crossbreeding/_status_effects.dm @@ -236,7 +236,7 @@ duration = -1 alert_type = null -datum/status_effect/rebreathing/tick() +/datum/status_effect/rebreathing/tick() owner.adjustOxyLoss(-6, 0) //Just a bit more than normal breathing. /////////////////////////////////////////////////////// @@ -469,6 +469,10 @@ datum/status_effect/rebreathing/tick() qdel(src) return ..() +/datum/status_effect/stabilized/Destroy() + linked_extract = null + return ..() + /datum/status_effect/stabilized/null //This shouldn't ever happen, but just in case. id = "stabilizednull" @@ -524,7 +528,7 @@ datum/status_effect/rebreathing/tick() ADD_TRAIT(owner, TRAIT_NOSLIPWATER, "slimestatus") return ..() -datum/status_effect/stabilized/blue/on_remove() +/datum/status_effect/stabilized/blue/on_remove() REMOVE_TRAIT(owner, TRAIT_NOSLIPWATER, "slimestatus") return ..() @@ -884,7 +888,8 @@ datum/status_effect/stabilized/blue/on_remove() /datum/status_effect/stabilized/oil/tick() if(owner.stat == DEAD) explosion(get_turf(owner),1,2,4,flame_range = 5) - owner.remove_status_effect(/datum/status_effect/stabilized/oil) + qdel(linked_extract) + return return ..() /datum/status_effect/stabilized/black diff --git a/code/modules/ruins/lavalandruin_code/alien_nest.dm b/code/modules/ruins/lavalandruin_code/alien_nest.dm index d98a6bf5c1..c204102b1b 100644 --- a/code/modules/ruins/lavalandruin_code/alien_nest.dm +++ b/code/modules/ruins/lavalandruin_code/alien_nest.dm @@ -5,6 +5,24 @@ name = "alien drone" mob_name = "alien drone" +/obj/effect/mob_spawn/alien/corpse/humanoid/hunter + mob_type = /mob/living/carbon/alien/humanoid/hunter + death = TRUE + name = "alien hunter" + mob_name = "alien hunter" + +/obj/effect/mob_spawn/alien/corpse/humanoid/sentinel + mob_type = /mob/living/carbon/alien/humanoid/sentinel + death = TRUE + name = "alien sentinel" + mob_name = "alien sentinel" + +/obj/effect/mob_spawn/alien/corpse/humanoid/praetorian + mob_type = /mob/living/carbon/alien/humanoid/royal/praetorian + death = TRUE + name = "alien praetorian" + mob_name = "alien praetorian" + /obj/effect/mob_spawn/alien/corpse/humanoid/queen mob_type = /mob/living/carbon/alien/humanoid/royal/queen death = TRUE diff --git a/code/modules/ruins/objects_and_mobs/necropolis_gate.dm b/code/modules/ruins/objects_and_mobs/necropolis_gate.dm index fd2f4377e7..5f9b810070 100644 --- a/code/modules/ruins/objects_and_mobs/necropolis_gate.dm +++ b/code/modules/ruins/objects_and_mobs/necropolis_gate.dm @@ -274,6 +274,7 @@ GLOBAL_DATUM(necropolis_gate, /obj/structure/necropolis_gate/legion_gate) return /obj/structure/stone_tile/Crossed(atom/movable/AM) + . = ..() if(falling || fallen) return var/turf/T = get_turf(src) diff --git a/code/modules/ruins/spaceruin_code/hilbertshotel.dm b/code/modules/ruins/spaceruin_code/hilbertshotel.dm index 4859fcfca0..33c4a70634 100644 --- a/code/modules/ruins/spaceruin_code/hilbertshotel.dm +++ b/code/modules/ruins/spaceruin_code/hilbertshotel.dm @@ -21,6 +21,9 @@ GLOBAL_VAR_INIT(hhmysteryRoomNumber, 1337) /obj/item/hilbertshotel/Initialize() . = ..() //Load templates + INVOKE_ASYNC(src, .proc/prepare_rooms) + +/obj/item/hilbertshotel/proc/prepare_rooms() hotelRoomTemp = new() hotelRoomTempEmpty = new() hotelRoomTempLore = new() diff --git a/code/modules/shuttle/emergency.dm b/code/modules/shuttle/emergency.dm index 15f9b31302..cc37fbbcc4 100644 --- a/code/modules/shuttle/emergency.dm +++ b/code/modules/shuttle/emergency.dm @@ -364,7 +364,9 @@ set waitfor = FALSE if(!SSdbcore.Connect()) return - var/datum/DBQuery/query_round_shuttle_name = SSdbcore.NewQuery("UPDATE [format_table_name("round")] SET shuttle_name = '[name]' WHERE id = [GLOB.round_id]") + var/datum/db_query/query_round_shuttle_name = SSdbcore.NewQuery({" + UPDATE [format_table_name("round")] SET shuttle_name = :name WHERE id = :round_id + "}, list("name" = name, "round_id" = GLOB.round_id)) query_round_shuttle_name.Execute() qdel(query_round_shuttle_name) @@ -396,7 +398,7 @@ return mode = SHUTTLE_DOCKED setTimer(SSshuttle.emergencyDockTime) - send2irc("Server", "The Emergency Shuttle has docked with the station.") + send2adminchat("Server", "The Emergency Shuttle has docked with the station.") priority_announce("The Emergency Shuttle has docked with the station. You have [timeLeft(600)] minutes to board the Emergency Shuttle.", null, "shuttledock", "Priority") ShuttleDBStuff() diff --git a/code/modules/shuttle/shuttle.dm b/code/modules/shuttle/shuttle.dm index 03ff509086..0eb7f6ee20 100644 --- a/code/modules/shuttle/shuttle.dm +++ b/code/modules/shuttle/shuttle.dm @@ -16,6 +16,8 @@ /// stationary ports and whatnot to tell them your ship's mobile /// port can be used in these places, or the docking port is compatible, etc. var/id + /// Possible destinations + var/port_destinations ///Common standard is for this to point -away- from the dockingport door, ie towards the ship dir = NORTH ///size of covered area, perpendicular to dir. You shouldn't modify this for mobile dockingports, set automatically. @@ -34,8 +36,26 @@ ///Delete this port after ship fly off. var/delete_after = FALSE -/obj/docking_port/proc/get_save_vars() - return list("pixel_x", "pixel_y", "dir", "name", "req_access", "req_access_txt", "piping_layer", "color", "icon_state", "pipe_color", "amount", "width", "height", "dwidth", "dheight") + ///are we registered in SSshuttles? + var/registered = FALSE + + ///register to SSshuttles +/obj/docking_port/proc/register() + if(registered) + WARNING("docking_port registered multiple times") + unregister() + registered = TRUE + return + + ///unregister from SSshuttles +/obj/docking_port/proc/unregister() + if(!registered) + WARNING("docking_port unregistered multiple times") + registered = FALSE + return + +/obj/docking_port/proc/Check_id() + return //these objects are indestructible /obj/docking_port/Destroy(force) @@ -43,7 +63,7 @@ // may result. if(force) ..() - . = QDEL_HINT_QUEUE + return QDEL_HINT_QUEUE else return QDEL_HINT_LETMELIVE @@ -60,7 +80,7 @@ /obj/docking_port/shuttleRotate() return //we don't rotate with shuttles via this code. -//returns a list(x0,y0, x1,y1) where points 0 and 1 are bounding corners of the projected rectangle +///returns a list(x0,y0, x1,y1) where points 0 and 1 are bounding corners of the projected rectangle /obj/docking_port/proc/return_coords(_x, _y, _dir) if(_dir == null) _dir = dir @@ -90,15 +110,14 @@ _y + (-dwidth+width-1)*sin + (-dheight+height-1)*cos ) -//returns turfs within our projected rectangle in no particular order +///returns turfs within our projected rectangle in no particular order /obj/docking_port/proc/return_turfs() var/list/L = return_coords() var/turf/T0 = locate(L[1],L[2],z) var/turf/T1 = locate(L[3],L[4],z) return block(T0,T1) -//returns turfs within our projected rectangle in a specific order. -//this ensures that turfs are copied over in the same order, regardless of any rotation +///returns turfs within our projected rectangle in a specific order.this ensures that turfs are copied over in the same order, regardless of any rotation /obj/docking_port/proc/return_ordered_turfs(_x, _y, _z, _dir) var/cos = 1 var/sin = 0 @@ -127,7 +146,9 @@ #ifdef DOCKING_PORT_HIGHLIGHT //Debug proc used to highlight bounding area -/obj/docking_port/proc/highlight(_color) +/obj/docking_port/proc/highlight(_color = "#f00") + invisibility = 0 + layer = GHOST_LAYER var/list/L = return_coords() var/turf/T0 = locate(L[1],L[2],z) var/turf/T1 = locate(L[3],L[4],z) @@ -146,11 +167,13 @@ /obj/docking_port/proc/get_docked() return locate(/obj/docking_port/stationary) in loc +// Return id of the docked docking_port /obj/docking_port/proc/getDockedId() var/obj/docking_port/P = get_docked() if(P) return P.id +// Say that A in the absolute (rectangular) bounds of this shuttle or no. /obj/docking_port/proc/is_in_shuttle_bounds(atom/A) var/turf/T = get_turf(A) if(T.z != z) @@ -174,13 +197,36 @@ var/datum/map_template/shuttle/roundstart_template var/json_key +/obj/docking_port/stationary/register(replace = FALSE) + . = ..() + if(!id) + id = "dock" + else + port_destinations = id + + if(!name) + name = "dock" + // how? + // It registers the initial shuttle (no changes) + // and if another one comes in with the same name (id) it adds the count on it + var/counter = SSshuttle.assoc_stationary[id] + if(!replace || !counter) + if(counter) + counter++ + SSshuttle.assoc_stationary[id] = counter + id = "[id]_[counter]" + name = "[name] [counter]" + else + SSshuttle.assoc_stationary[id] = 1 + + if(!port_destinations) + port_destinations = id + + SSshuttle.stationary += src + /obj/docking_port/stationary/Initialize(mapload) . = ..() - SSshuttle.stationary += src - if(!id) - id = "[SSshuttle.stationary.len]" - if(name == "dock") - name = "dock[SSshuttle.stationary.len]" + register() if(!area_type) var/area/place = get_area(src) area_type = place?.type || SHUTTLE_DEFAULT_UNDERLYING_AREA // We might be created in nullspace @@ -193,9 +239,13 @@ highlight("#f00") #endif +/obj/docking_port/stationary/unregister() + . = ..() + SSshuttle.stationary -= src + /obj/docking_port/stationary/Destroy(force) if(force) - SSshuttle.stationary -= src + unregister() . = ..() /obj/docking_port/stationary/Moved(atom/oldloc, dir, forced) @@ -249,6 +299,28 @@ reserved_area = null return ..() +/obj/docking_port/stationary/picked + ///Holds a list of map name strings for the port to pick from + var/list/shuttlekeys + +/obj/docking_port/stationary/picked/Initialize(mapload) + . = ..() + if(!LAZYLEN(shuttlekeys)) + WARNING("Random docking port [id] loaded with no shuttle keys") + return + var/selectedid = pick(shuttlekeys) + roundstart_template = SSmapping.shuttle_templates[selectedid] + +/obj/docking_port/stationary/picked/whiteship + name = "Deep Space" + id = "whiteship_away" + dheight = 0 + dir = 2 + dwidth = 11 + height = 22 + width = 35 + shuttlekeys = list("whiteship_meta", "whiteship_pubby", "whiteship_box", "whiteship_cere", "whiteship_kilo", "whiteship_donut", "whiteship_delta") + /obj/docking_port/mobile name = "shuttle" icon_state = "pinonclose" @@ -271,11 +343,9 @@ /// time spent after transit 'landing' before actually arriving var/prearrivalTime = 0 - /// The direction the shuttle prefers to travel in, ie what direction - /// the animation will cause it to appear to be traveling in + /// The direction the shuttle prefers to travel in, ie what direction the animation will cause it to appear to be traveling in var/preferred_direction = NORTH - /// relative direction of the docking port from the front of the shuttle - /// NORTH is towards front, EAST would be starboard side, WEST port, etc. + /// relative direction of the docking port from the front of the shuttle. NORTH is towards front, EAST would be starboard side, WEST port, etc. var/port_direction = NORTH var/obj/docking_port/stationary/destination @@ -297,15 +367,38 @@ var/can_move_docking_ports = FALSE var/list/hidden_turfs = list() -/obj/docking_port/mobile/proc/register() +/obj/docking_port/mobile/register(replace = FALSE) + . = ..() + if(!id) + id = "shuttle" + + if(!name) + name = "shuttle" + + var/counter = SSshuttle.assoc_mobile[id] + if(!replace || !counter) + if(counter) + counter++ + SSshuttle.assoc_mobile[id] = counter + id = "[id]_[counter]" + name = "[name] [counter]" + //Re link machinery to new shuttle id + linkup() + else + SSshuttle.assoc_mobile[id] = 1 + SSshuttle.mobile += src +/obj/docking_port/mobile/unregister() + . = ..() + SSshuttle.mobile -= src + /obj/docking_port/mobile/Destroy(force) if(force) - SSshuttle.mobile -= src + unregister() destination = null previous = null - QDEL_NULL(assigned_transit) //don't need it where we're goin'! + QDEL_NULL(assigned_transit) //don't need it where we're goin'! shuttle_areas = null remove_ripples() . = ..() @@ -314,9 +407,16 @@ . = ..() if(!id) - id = "[SSshuttle.mobile.len]" - if(name == "shuttle") - name = "shuttle[SSshuttle.mobile.len]" + id = "shuttle" + if(!name) + name = "shuttle" + var/counter = 1 + var/tmp_id = id + var/tmp_name = name + while(Check_id(id)) + counter++ + id = "[tmp_id]_[counter]" + name = "[tmp_name] [counter]" shuttle_areas = list() var/list/all_turfs = return_ordered_turfs(x, y, z, dir) @@ -334,20 +434,13 @@ #endif // Called after the shuttle is loaded from template -/obj/docking_port/mobile/proc/linkup(datum/map_template/shuttle/template, obj/docking_port/stationary/dock) - var/list/static/shuttle_id = list() - var/idnum = ++shuttle_id[template] - if(idnum > 1) - if(id == initial(id)) - id = "[id][idnum]" - if(name == initial(name)) - name = "[name] [idnum]" +/obj/docking_port/mobile/proc/linkup(obj/docking_port/stationary/dock) for(var/place in shuttle_areas) var/area/area = place - area.connect_to_shuttle(src, dock, idnum, FALSE) + area.connect_to_shuttle(src, dock) for(var/each in place) var/atom/atom = each - atom.connect_to_shuttle(src, dock, idnum, FALSE) + atom.connect_to_shuttle(src, dock) //this is a hook for custom behaviour. Maybe at some point we could add checks to see if engines are intact @@ -442,7 +535,7 @@ mode = SHUTTLE_RECALL /obj/docking_port/mobile/proc/enterTransit() - if((SSshuttle.lockdown && is_station_level(z)) || !canMove()) //emp went off, no escape + if((SSshuttle.lockdown && is_station_level(z)) || !canMove()) //emp went off, no escape mode = SHUTTLE_IDLE return previous = null @@ -454,7 +547,7 @@ if(S1) if(initiate_docking(S1) != DOCKING_SUCCESS) WARNING("shuttle \"[id]\" could not enter transit space. Docked at [S0 ? S0.id : "null"]. Transit dock [S1 ? S1.id : "null"].") - else + else if(S0) if(S0.delete_after) qdel(S0, TRUE) else @@ -471,7 +564,7 @@ var/underlying_area_type = SHUTTLE_DEFAULT_UNDERLYING_AREA // If the shuttle is docked to a stationary port, restore its normal // "empty" area and turf - if(current_dock && current_dock.area_type) + if(current_dock?.area_type) underlying_area_type = current_dock.area_type var/list/old_turfs = return_ordered_turfs(x, y, z, dir) @@ -489,7 +582,7 @@ oldT.change_area(old_area, underlying_area) oldT.empty(FALSE) - // Here we locate the bottomost shuttle boundary and remove all turfs above it + // Here we locate the bottommost shuttle boundary and remove all turfs above it var/list/baseturf_cache = oldT.baseturfs for(var/k in 1 to length(baseturf_cache)) if(ispath(baseturf_cache[k], /turf/baseturf_skipover/shuttle)) @@ -626,7 +719,7 @@ for(var/place in shuttle_areas) var/area/shuttle/shuttle_area = place shuttle_area.parallax_movedir = FALSE - if(assigned_transit && assigned_transit.assigned_area) + if(assigned_transit?.assigned_area) assigned_transit.assigned_area.parallax_movedir = FALSE var/list/L0 = return_ordered_turfs(x, y, z, dir) for (var/thing in L0) @@ -696,11 +789,13 @@ return "RCH" if(SHUTTLE_PREARRIVAL) return "LDN" + // if(SHUTTLE_DISABLED) + // return "DIS" return "" // returns 5-letter timer string, used by status screens and mob status panel /obj/docking_port/mobile/proc/getTimerStr() - if(mode == SHUTTLE_STRANDED) + if(mode == SHUTTLE_STRANDED)// || mode == SHUTTLE_DISABLED) return "--:--" var/timeleft = timeLeft() @@ -712,8 +807,8 @@ return "00:00" /** - * Gets shuttle location status in a form of string for tgui interfaces - */ + * Gets shuttle location status in a form of string for tgui interfaces + */ /obj/docking_port/mobile/proc/get_status_text_tgui() var/obj/docking_port/stationary/dockedAt = get_docked() var/docked_at = dockedAt?.name || "Unknown" @@ -727,7 +822,8 @@ else dst = destination return "In transit to [dst?.name || "unknown location"]" - else if(mode == SHUTTLE_RECHARGING) + // custom shuttle + if(mode == SHUTTLE_RECHARGING) return "[docked_at], recharging [getTimerStr()]" else return docked_at @@ -752,7 +848,7 @@ /obj/docking_port/mobile/proc/getDbgStatusText() var/obj/docking_port/stationary/dockedAt = get_docked() - . = (dockedAt && dockedAt.name) ? dockedAt.name : "unknown" + . = (dockedAt?.name) ? dockedAt.name : "unknown" if(istype(dockedAt, /obj/docking_port/stationary/transit)) var/obj/docking_port/stationary/dst if(mode == SHUTTLE_RECALL) @@ -795,7 +891,7 @@ var/range = (engine_coeff * max(width, height)) var/long_range = range * 2.5 var/atom/distant_source - if(LAZYLEN(engine_list)) + if(engine_list[1]) distant_source = engine_list[1] else for(var/A in areas) @@ -807,7 +903,7 @@ for(var/mob/M in SSmobs.clients_by_zlevel[z]) var/dist_far = get_dist(M, distant_source) if(dist_far <= long_range && dist_far > range) - M.playsound_local(distant_source, "sound/effects/[selected_sound]_distance.ogg", 100, falloff = 20) + M.playsound_local(distant_source, "sound/effects/[selected_sound]_distance.ogg", 100) else if(dist_far <= range) var/source if(engine_list.len == 0) @@ -819,7 +915,7 @@ if(dist_near < closest_dist) source = O closest_dist = dist_near - M.playsound_local(source, "sound/effects/[selected_sound].ogg", 100, falloff = range / 2) + M.playsound_local(source, "sound/effects/[selected_sound].ogg", 100) // Losing all initial engines should get you 2 // Adding another set of engines at 0.5 time @@ -841,10 +937,6 @@ if(!QDELETED(E)) engine_list += E . += E.engine_power - for(var/obj/machinery/shuttle/engine/E in areaInstance.contents) - if(!QDELETED(E)) - engine_list += E - . += E.thruster_active ? 1 : 0 // Double initial engines to get to 0.5 minimum // Lose all initial engines to get to 2 diff --git a/code/modules/shuttle/shuttle_creation/shuttle_creator.dm b/code/modules/shuttle/shuttle_creation/shuttle_creator.dm index b3d99f22ab..b9374a09c4 100644 --- a/code/modules/shuttle/shuttle_creation/shuttle_creator.dm +++ b/code/modules/shuttle/shuttle_creation/shuttle_creator.dm @@ -178,12 +178,12 @@ GLOBAL_LIST_EMPTY(custom_shuttle_machines) //Machines that require updating (He to_chat(user, "Invalid shuttle, restarting bluespace systems...") return FALSE - var/datum/map_template/shuttle/new_shuttle = new /datum/map_template/shuttle() + // var/datum/map_template/shuttle/new_shuttle = new /datum/map_template/shuttle() var/obj/docking_port/mobile/port = new /obj/docking_port/mobile(get_turf(target)) var/obj/docking_port/stationary/stationary_port = new /obj/docking_port/stationary(get_turf(target)) port.callTime = 50 - port.dir = 1 //Point away from space. + port.dir = NORTH //Point away from space. port.id = "custom_[GLOB.custom_shuttle_count]" linkedShuttleId = port.id port.ignitionTime = 25 @@ -228,7 +228,8 @@ GLOBAL_LIST_EMPTY(custom_shuttle_machines) //Machines that require updating (He curT.baseturfs.Insert(3, /turf/baseturf_skipover/shuttle) port.shuttle_areas[cur_area] = TRUE - port.linkup(new_shuttle, stationary_port) + port.register() // register does the same thing on the old linkup + port.linkup(stationary_port) port.movement_force = list("KNOCKDOWN" = 0, "THROW" = 0) port.initiate_docking(stationary_port) @@ -236,8 +237,6 @@ GLOBAL_LIST_EMPTY(custom_shuttle_machines) //Machines that require updating (He port.mode = SHUTTLE_IDLE port.timer = 0 - port.register() - icon_state = "rsd_empty" //Clear highlights diff --git a/code/modules/shuttle/spaceship_navigation_beacon.dm b/code/modules/shuttle/spaceship_navigation_beacon.dm index f1861e0477..dbf81d791e 100644 --- a/code/modules/shuttle/spaceship_navigation_beacon.dm +++ b/code/modules/shuttle/spaceship_navigation_beacon.dm @@ -21,7 +21,7 @@ . = ..() SSshuttle.beacons |= src -obj/machinery/spaceship_navigation_beacon/emp_act() +/obj/machinery/spaceship_navigation_beacon/emp_act() locked = TRUE /obj/machinery/spaceship_navigation_beacon/Destroy() @@ -39,7 +39,9 @@ obj/machinery/spaceship_navigation_beacon/emp_act() . = ..() update_icon() -/obj/machinery/spaceship_navigation_beacon/multitool_act(mob/living/user, obj/item/multitool/I) +/obj/machinery/spaceship_navigation_beacon/multitool_act(mob/living/user, obj/item/I) + if(!I.tool_behaviour == TOOL_MULTITOOL) + return if(panel_open) var/new_name = "Beacon_[input("Enter the custom name for this beacon", "It be Beacon ..your input..") as text]" if(new_name && Adjacent(user)) @@ -60,4 +62,4 @@ obj/machinery/spaceship_navigation_beacon/emp_act() if(default_deconstruction_crowbar(W)) return - return ..() \ No newline at end of file + return ..() diff --git a/code/modules/smithing/finished_items.dm b/code/modules/smithing/finished_items.dm index 5db6b09452..20eaee8a08 100644 --- a/code/modules/smithing/finished_items.dm +++ b/code/modules/smithing/finished_items.dm @@ -231,7 +231,7 @@ /obj/item/melee/smith/twohand/katana name = "katana" - icon_state = "katana" + icon_state = "katana-s" overlay_state = "katanahilt" force = 7 wielded_mult = 2 diff --git a/code/modules/spells/spell_types/shapeshift.dm b/code/modules/spells/spell_types/shapeshift.dm index e513865246..67c2e3e941 100644 --- a/code/modules/spells/spell_types/shapeshift.dm +++ b/code/modules/spells/spell_types/shapeshift.dm @@ -78,7 +78,7 @@ desc = "Take on the shape a lesser ash drake." invocation = "RAAAAAAAAWR!" - shapeshift_type = /mob/living/simple_animal/hostile/megafauna/dragon/lesser + shapeshift_type = /mob/living/simple_animal/hostile/megafauna/dragon/lesser/transformed /obj/shapeshift_holder diff --git a/code/modules/spells/spell_types/spacetime_distortion.dm b/code/modules/spells/spell_types/spacetime_distortion.dm index 5797cbf8b7..5a8776b16b 100644 --- a/code/modules/spells/spell_types/spacetime_distortion.dm +++ b/code/modules/spells/spell_types/spacetime_distortion.dm @@ -1,6 +1,6 @@ /obj/effect/proc_holder/spell/spacetime_dist name = "Spacetime Distortion" - desc = "Entangle the strings of spacetime to deny easy movement around you. The strings vibrate..." + desc = "Entangle the strings of space-time in an area around you, randomizing the layout and making proper movement impossible. The strings vibrate..." charge_max = 300 var/duration = 150 range = 7 @@ -10,8 +10,9 @@ sound = 'sound/effects/magic.ogg' cooldown_min = 300 level_max = 0 + // action_icon_state = "spacetime" -/obj/effect/proc_holder/spell/spacetime_dist/can_cast(mob/user = usr, skipcharge = FALSE, silent = FALSE) +/obj/effect/proc_holder/spell/spacetime_dist/can_cast(mob/user = usr) if(ready) return ..() return FALSE @@ -97,10 +98,11 @@ busy = TRUE flick("purplesparkles", src) AM.forceMove(get_turf(src)) - playsound(get_turf(src),sound,70,0) + playsound(get_turf(src),sound,70,FALSE) busy = FALSE /obj/effect/cross_action/spacetime_dist/Crossed(atom/movable/AM) + . = ..() if(!busy) walk_link(AM) @@ -110,7 +112,8 @@ else walk_link(user) -/obj/effect/cross_action/spacetime_dist/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags) +//ATTACK HAND IGNORING PARENT RETURN VALUE +/obj/effect/cross_action/spacetime_dist/on_attack_hand(mob/user) walk_link(user) /obj/effect/cross_action/spacetime_dist/attack_paw(mob/user) diff --git a/code/modules/station_goals/bsa.dm b/code/modules/station_goals/bsa.dm index 9ebcfe91d6..cb1fdcfc2e 100644 --- a/code/modules/station_goals/bsa.dm +++ b/code/modules/station_goals/bsa.dm @@ -41,9 +41,8 @@ icon_state = "power_box" /obj/machinery/bsa/back/multitool_act(mob/living/user, obj/item/I) - if(istype(I, /obj/item/multitool)) // Only this multitool type has a data buffer. - var/obj/item/multitool/M = I - M.buffer = src + if(I.tool_behaviour == TOOL_MULTITOOL) // Lies and deception + I.buffer = src to_chat(user, "You store linkage information in [I]'s buffer.") else to_chat(user, "[I] has no data buffer!") @@ -55,9 +54,8 @@ icon_state = "emitter_center" /obj/machinery/bsa/front/multitool_act(mob/living/user, obj/item/I) - if(istype(I, /obj/item/multitool)) // Only this multitool type has a data buffer. - var/obj/item/multitool/M = I - M.buffer = src + if(I.tool_behaviour == TOOL_MULTITOOL) // Lies and deception + I.buffer = src to_chat(user, "You store linkage information in [I]'s buffer.") else to_chat(user, "[I] has no data buffer!") @@ -71,16 +69,15 @@ var/obj/machinery/bsa/front/front /obj/machinery/bsa/middle/multitool_act(mob/living/user, obj/item/I) - if(istype(I, /obj/item/multitool)) // Only this multitool type has a data buffer. - var/obj/item/multitool/M = I - if(M.buffer) - if(istype(M.buffer, /obj/machinery/bsa/back)) - back = M.buffer - M.buffer = null + if(I.tool_behaviour == TOOL_MULTITOOL) // Lies and deception + if(I.buffer) + if(istype(I.buffer, /obj/machinery/bsa/back)) + back = I.buffer + I.buffer = null to_chat(user, "You link [src] with [back].") - else if(istype(M.buffer, /obj/machinery/bsa/front)) - front = M.buffer - M.buffer = null + else if(istype(I.buffer, /obj/machinery/bsa/front)) + front = I.buffer + I.buffer = null to_chat(user, "You link [src] with [front].") else to_chat(user, "[I]'s data buffer is empty!") diff --git a/code/modules/station_goals/dna_vault.dm b/code/modules/station_goals/dna_vault.dm index 4ac3777a41..b13c2df770 100644 --- a/code/modules/station_goals/dna_vault.dm +++ b/code/modules/station_goals/dna_vault.dm @@ -33,14 +33,14 @@ /datum/station_goal/dna_vault/get_report() return {"Our long term prediction systems indicate a 99% chance of system-wide cataclysm in the near future. - We need you to construct a DNA Vault aboard your station. + We need you to construct a DNA Vault aboard your station. - The DNA Vault needs to contain samples of: - [animal_count] unique animal data - [plant_count] unique non-standard plant data - [human_count] unique sapient humanoid DNA data + The DNA Vault needs to contain samples of: + [animal_count] unique animal data + [plant_count] unique non-standard plant data + [human_count] unique sapient humanoid DNA data - Base vault parts are available for shipping via cargo."} + Base vault parts are available for shipping via cargo."} /datum/station_goal/dna_vault/on_report() @@ -87,7 +87,7 @@ if(!H.myseed) return if(!H.harvest)// So it's bit harder. - to_chat(user, "Plant needs to be ready to harvest to perform full data scan.") //Because space dna is actually magic + to_chat(user, "Plant needs to be ready to harvest to perform full data scan.") //Because space dna is actually magic return if(plants[H.myseed.type]) to_chat(user, "Plant data already present in local storage.") @@ -101,10 +101,10 @@ if(isanimal(target)) var/mob/living/simple_animal/A = target if(!A.healable)//simple approximation of being animal not a robot or similar - to_chat(user, "No compatible DNA detected") + to_chat(user, "No compatible DNA detected.") return if(animals[target.type]) - to_chat(user, "Animal data already present in local storage.") + to_chat(user, "Animal data already present in local storage.") return animals[target.type] = 1 to_chat(user, "Animal data added to local storage.") @@ -173,7 +173,6 @@ qdel(filler) . = ..() - /obj/machinery/dna_vault/ui_interact(mob/user, datum/tgui/ui) ui = SStgui.try_update_ui(user, src, ui) if(!ui) @@ -204,15 +203,17 @@ data["choiceB"] = "" if(user && completed) var/list/L = power_lottery[user] - if(L && L.len) + if(L?.len) data["used"] = FALSE data["choiceA"] = L[1] data["choiceB"] = L[2] return data /obj/machinery/dna_vault/ui_act(action, params) - if(..()) + . = ..() + if(.) return + switch(action) if("gene") upgrade(usr,params["choice"]) @@ -244,8 +245,6 @@ else return ..() - - /obj/machinery/dna_vault/proc/upgrade(mob/living/carbon/human/H,upgrade_type) if(!(upgrade_type in power_lottery[H])) return diff --git a/code/modules/station_goals/shield.dm b/code/modules/station_goals/shield.dm index c8fbda8988..e317820e7d 100644 --- a/code/modules/station_goals/shield.dm +++ b/code/modules/station_goals/shield.dm @@ -122,7 +122,7 @@ icon_state = active ? "sat_active" : "sat_inactive" /obj/machinery/satellite/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/multitool)) + if(I.tool_behaviour == TOOL_MULTITOOL) to_chat(user, "// NTSAT-[id] // Mode : [active ? "PRIMARY" : "STANDBY"] //[(obj_flags & EMAGGED) ? "DEBUG_MODE //" : ""]") else return ..() diff --git a/code/modules/surgery/advanced/toxichealing.dm b/code/modules/surgery/advanced/toxichealing.dm index 0e0fd10c1c..376fb43c31 100644 --- a/code/modules/surgery/advanced/toxichealing.dm +++ b/code/modules/surgery/advanced/toxichealing.dm @@ -24,6 +24,13 @@ /datum/surgery_step/toxichealing/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) user.visible_message("[user] starts rejuvenating some of [target]'s flesh back to life.", "You start knitting some of [target]'s flesh back to life.") +/datum/surgery_step/toxichealing/initiate(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, try_to_fail = FALSE) + if(..()) + while((target.getToxLoss() >= 1) || (target.getOxyLoss() >= 1)) + . = ..() + if(!.) + break + /datum/surgery_step/toxichealing/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) user.visible_message("[user] fixes some of [target]'s wounds.", "You succeed in fixing some of [target]'s wounds.") target.heal_bodypart_damage(0,0,30) //Heals stam diff --git a/code/modules/surgery/bodyparts/_bodyparts.dm b/code/modules/surgery/bodyparts/_bodyparts.dm index 2ca7d07e73..962bc8c1ce 100644 --- a/code/modules/surgery/bodyparts/_bodyparts.dm +++ b/code/modules/surgery/bodyparts/_bodyparts.dm @@ -297,6 +297,7 @@ owner.update_stamina() consider_processing() update_disabled() + update_threshhold_state() return update_bodypart_damage_state() /// Allows us to roll for and apply a wound without actually dealing damage. Used for aggregate wounding power with pellet clouds @@ -475,6 +476,7 @@ owner.updatehealth() consider_processing() update_disabled() + update_threshhold_state() return update_bodypart_damage_state() //Returns total damage. diff --git a/code/modules/surgery/bodyparts/robot_bodyparts.dm b/code/modules/surgery/bodyparts/robot_bodyparts.dm index d08d8bfa09..07b7ae1456 100644 --- a/code/modules/surgery/bodyparts/robot_bodyparts.dm +++ b/code/modules/surgery/bodyparts/robot_bodyparts.dm @@ -209,7 +209,7 @@ else src.flash1 = F to_chat(user, "You insert the flash into the eye socket.") - else if(istype(W, /obj/item/crowbar)) + else if(W.tool_behaviour == TOOL_CROWBAR) if(flash1 || flash2) W.play_tool_sound(src) to_chat(user, "You remove the flash from [src].") diff --git a/code/modules/surgery/dental_implant.dm b/code/modules/surgery/dental_implant.dm index 311c886c9d..f41f299439 100644 --- a/code/modules/surgery/dental_implant.dm +++ b/code/modules/surgery/dental_implant.dm @@ -36,6 +36,6 @@ log_combat(owner, null, "swallowed an implanted pill", target) if(target.reagents.total_volume) target.reagents.reaction(owner, INGEST) - target.reagents.trans_to(owner, target.reagents.total_volume, log = TRUE) + target.reagents.trans_to(owner, target.reagents.total_volume, log = "dental pill swallow") qdel(target) return 1 diff --git a/code/modules/surgery/organs/eyes.dm b/code/modules/surgery/organs/eyes.dm index a088eb2e4f..09f0a901a3 100644 --- a/code/modules/surgery/organs/eyes.dm +++ b/code/modules/surgery/organs/eyes.dm @@ -404,6 +404,7 @@ on_mob.set_light(1, 1, current_color_string) /obj/effect/abstract/eye_lighting + mouse_opacity = MOUSE_OPACITY_TRANSPARENT var/obj/item/organ/eyes/robotic/glow/parent /obj/effect/abstract/eye_lighting/Initialize() diff --git a/code/modules/surgery/organs/heart.dm b/code/modules/surgery/organs/heart.dm index 6b3d47b587..aaa4e34f36 100644 --- a/code/modules/surgery/organs/heart.dm +++ b/code/modules/surgery/organs/heart.dm @@ -97,7 +97,7 @@ owner.set_heartattack(TRUE) failed = TRUE -obj/item/organ/heart/slime +/obj/item/organ/heart/slime name = "slime heart" desc = "It seems we've gotten to the slimy core of the matter." icon_state = "heart-s-on" @@ -221,7 +221,7 @@ obj/item/organ/heart/slime var/rid = /datum/reagent/medicine/epinephrine var/ramount = 10 -obj/item/organ/heart/cybernetic/upgraded/on_life() +/obj/item/organ/heart/cybernetic/upgraded/on_life() . = ..() if(!.) return diff --git a/code/modules/surgery/organs/tongue.dm b/code/modules/surgery/organs/tongue.dm index a3edfc2887..7cba3d358a 100644 --- a/code/modules/surgery/organs/tongue.dm +++ b/code/modules/surgery/organs/tongue.dm @@ -25,6 +25,7 @@ /datum/language/slime, /datum/language/vampiric, /datum/language/dwarf, + /datum/language/signlanguage, )) healing_factor = STANDARD_ORGAN_HEALING*5 //Fast!! decay_factor = STANDARD_ORGAN_DECAY/2 diff --git a/code/modules/surgery/organs/vocal_cords.dm b/code/modules/surgery/organs/vocal_cords.dm index b6b74efe32..0e6456e8b0 100644 --- a/code/modules/surgery/organs/vocal_cords.dm +++ b/code/modules/surgery/organs/vocal_cords.dm @@ -1,4 +1,5 @@ #define COOLDOWN_STUN 1200 +#define COOLDOWN_KNOCKDOWN 600 #define COOLDOWN_DAMAGE 600 #define COOLDOWN_MEME 300 #define COOLDOWN_NONE 100 @@ -213,7 +214,6 @@ var/static/regex/stun_words = regex("stop|wait|stand still|hold on|halt") var/static/regex/knockdown_words = regex("drop|fall|trip|knockdown") - var/static/regex/sleep_words = regex("sleep|slumber|rest") var/static/regex/vomit_words = regex("vomit|throw up|sick") var/static/regex/silence_words = regex("shut up|silence|be silent|ssh|quiet|hush") var/static/regex/hallucinate_words = regex("see the truth|hallucinate") @@ -264,26 +264,20 @@ cooldown = COOLDOWN_STUN for(var/V in listeners) var/mob/living/L = V - L.Stun(60 * power_multiplier) + L.Stagger(60 * power_multiplier) //KNOCKDOWN else if(findtext(message, knockdown_words)) - cooldown = COOLDOWN_STUN + cooldown = COOLDOWN_KNOCKDOWN for(var/V in listeners) var/mob/living/L = V - L.DefaultCombatKnockdown(60 * power_multiplier) - - //SLEEP - else if((findtext(message, sleep_words))) - cooldown = COOLDOWN_STUN - for(var/mob/living/carbon/C in listeners) - C.Sleeping(40 * power_multiplier) + L.DefaultCombatKnockdown() //VOMIT else if((findtext(message, vomit_words))) - cooldown = COOLDOWN_STUN + cooldown = COOLDOWN_DAMAGE for(var/mob/living/carbon/C in listeners) - C.vomit(10 * power_multiplier, distance = power_multiplier) + C.vomit(10 * power_multiplier, distance = power_multiplier, stun = FALSE) //SILENCE else if((findtext(message, silence_words))) @@ -835,7 +829,7 @@ if(HAS_TRAIT(L, TRAIT_MASO)) if(ishuman(L)) var/mob/living/carbon/human/H = L - H.adjust_arousal(3*power_multiplier,maso = TRUE) + H.adjust_arousal(3*power_multiplier,"velvet speech", maso = TRUE) descmessage += "And yet, it feels so good..!" //I don't really understand masco, is this the right sort of thing they like? E.enthrallTally += power_multiplier E.resistanceTally -= power_multiplier @@ -1319,7 +1313,7 @@ if(E.phase > 1) if(user.ckey == E.enthrallID && user.real_name == E.master.real_name) E.master = user - addtimer(CALLBACK(GLOBAL_PROC, .proc/to_chat, H, "[(E.lewd?"You hear the words of your [E.enthrallGender] again!! They're back!!":"You recognise the voice of [E.master].")]
    "), 5) + to_chat(H, "[(E.lewd?"You hear the words of your [E.enthrallGender] again!! They're back!!":"You recognise the voice of [E.master].")]") to_chat(user, "[H] looks at you with sparkling eyes, recognising you!") //I dunno how to do state objectives without them revealing they're an antag diff --git a/code/modules/surgery/surgery_step.dm b/code/modules/surgery/surgery_step.dm index 900d452547..456d93c73b 100644 --- a/code/modules/surgery/surgery_step.dm +++ b/code/modules/surgery/surgery_step.dm @@ -83,11 +83,14 @@ surgery.complete() surgery.step_in_progress = FALSE return advance - else - surgery.step_in_progress = FALSE - if(repeatable) - return FALSE //This is how the repeatable surgery detects it shouldn't cycle - return TRUE //Stop the attack chain! - Except on repeatable steps, because otherwise we land in an infinite loop. + + if(target.stat == DEAD && user.client) + user.client.give_award(/datum/award/achievement/misc/sandman, user) + + surgery.step_in_progress = FALSE + if(repeatable) + return FALSE //This is how the repeatable surgery detects it shouldn't cycle + return TRUE //Stop the attack chain! - Except on repeatable steps, because otherwise we land in an infinite loop. /datum/surgery_step/proc/preop(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery) diff --git a/code/modules/tcg/cards.dm b/code/modules/tcg/cards.dm new file mode 100644 index 0000000000..f5c7c47aaf --- /dev/null +++ b/code/modules/tcg/cards.dm @@ -0,0 +1,822 @@ +#define TAPPED_ANGLE 90 +#define UNTAPPED_ANGLE 0 + +#define COMMON_SERIES list(/datum/tcg_card/pack_1, /datum/tcg_card/exodia) //So star cards don't drop + +/datum/tcg_card + var/name = "Stupid Coder" + var/desc = "A coder that fucked up this card. Report if you see this." + var/rules = "Tap this card. It will ahelp itself" + var/icon_state = "cardback" + var/pack = 'icons/obj/tcg/pack_1.dmi' + + var/mana_cost = 0 + var/attack = 0 + var/health = 0 + + var/faction = "Coderbus" + var/rarity = "Stoopid" + var/card_type = "Unit" + + var/obj/item/tcg_card/card + +/*Uncomment if you want to make the game automatic + +/datum/tcg_card/proc/Use(datum/tcg_card/affected_card, mob/living/user) + if(card_type == "Equipment") + affected_card.health += health + affected_card.attack += attack + to_chat(user, "You use [card] on [affected_card.card], upgrading it's stats.") + user.emote("uses [card] on [affected_card.card], upgrading it's stats.") //To get that visible emote. Useful if you want nice gameplay + else if (card_type == "Unit") + affected_card.health -= attack + health -= affected_card.attack + var/flavortext = "." + if(affected_card.health <= 0) + flavortext = ", killing [affected_card.card]!" + if(health <= 0) + flavortext = ", killing both [affected_card.card] and [card]!" + else + flavortext = ", killing [card] in the process!" + to_chat(user, "You attack [affected_card.card] with [card][flavortext]") + user.emote("attacks [affected_card.card] with [card][flavortext]") + +*/ + +/datum/tcg_card/proc/UseSelf(mob/living/user) + return + +/datum/tcg_card/proc/Tap(mob/living/user) //Actually runtimes on tap! Tapping is basically disabling a card for a turn in exchange for special effects + if(type == /datum/tcg_card) + log_runtime("[user] managed to get a blank TCG card.") + +/datum/tcg_card/proc/Untap(mob/living/user) + return + +/datum/tcg_card/proc/Reset(mob/living/user) + to_chat(user, "You reset [card]'s stats to original.") + mana_cost = initial(mana_cost) + rules = initial(rules) + health = initial(health) + attack = initial(attack) + faction = initial(faction) + +/obj/item/tcg_card + name = "TCG card" + desc = "A flipped TCG-branded card." + icon_state = "cardback" + icon = 'icons/obj/tcg/pack_1.dmi' + + var/datum_type = /datum/tcg_card + var/datum/tcg_card/card_datum + + w_class = WEIGHT_CLASS_TINY + + var/flipped = FALSE + var/tapped = FALSE + var/special = FALSE + var/illegal = FALSE + +/obj/item/tcg_card/special + special = TRUE + +/obj/item/tcg_card/examine(mob/user) + . = ..() + sleep(2) //So it prints this shit after the examine + if(flipped) + return + to_chat(user, "This card has following stats:") + to_chat(user, "Mana cost: [card_datum.mana_cost]") + to_chat(user, "Health: [card_datum.health]") + to_chat(user, "Attack: [card_datum.attack]") + to_chat(user, "Faction: [card_datum.faction]") + to_chat(user, "Rarity: [card_datum.rarity]") + to_chat(user, "Card Type: [card_datum.card_type]") + to_chat(user, "It's effect is: [card_datum.rules]") + if(illegal) + to_chat(user, "It's a low-quality copy of a real card. TCG Gaming Community won't probably accept it.") //Doesn't do crap, just for lulz + +/obj/item/tcg_card/openTip(location, control, params, user) //Overriding for nice UI + if(flipped) + return ..() + var/desc_content = "[desc]
    \ + This card has following stats:
    \ + Mana cost: [card_datum.mana_cost]
    \ + Health: [card_datum.health]
    \ + Attack: [card_datum.attack]
    \ + Faction: [card_datum.faction]
    \ + Rarity: [card_datum.rarity]
    \ + Card Type: [card_datum.card_type]
    \ + It's effect is: [card_datum.rules]" + openToolTip(user,src,params,title = name,content = desc_content,theme = "") + +/obj/item/tcg_card/New(loc, new_datum, illegal_card = FALSE) + . = ..() + if(!special) + datum_type = new_datum + card_datum = new datum_type + icon = card_datum.pack + icon_state = card_datum.icon_state + name = card_datum.name + desc = card_datum.desc + illegal = illegal_card + + switch(card_datum.rarity) + if("Common") + grind_results = list(/datum/reagent/card_powder/green = 1) + if("Rare") + grind_results = list(/datum/reagent/card_powder/blue = 1) + if("Epic") + grind_results = list(/datum/reagent/card_powder/purple = 1) + if("Legendary") + grind_results = list(/datum/reagent/card_powder/yellow = 1) + if("Exodia") + grind_results = list(/datum/reagent/card_powder/black = 1) + +/obj/item/tcg_card/attack_hand(mob/user) + var/list/possible_actions = list( + "Pick Up" = image(icon = 'icons/mob/radial.dmi', icon_state = "radial_pickup"), + "Tap" = image(icon = 'icons/mob/radial.dmi', icon_state = "radial_tap"), + "Flip" = image(icon = 'icons/mob/radial.dmi', icon_state = "radial_flip") + ) + var/result = show_radial_menu(user, src, possible_actions, require_near = TRUE, tooltips = TRUE) + switch(result) + if("Pick Up") + . = ..() + if("Flip") + flipped = !flipped + if(flipped) + icon_state = "cardback" + name = "TCG card" + desc = "A flipped TCG-branded card." + else + name = card_datum.name + desc = card_datum.desc + icon_state = card_datum.icon_state + if("Tap") + var/matrix/ntransform = matrix(transform) + if(tapped) + ntransform.TurnTo(TAPPED_ANGLE , UNTAPPED_ANGLE) + else + ntransform.TurnTo(UNTAPPED_ANGLE , TAPPED_ANGLE) + tapped = !tapped + animate(src, transform = ntransform, time = 2, easing = (EASE_IN|EASE_OUT)) + if(tapped) + card_datum.Tap(user) + else + card_datum.Untap(user) + +/obj/item/tcg_card/attackby(obj/item/I, mob/living/user, params) + if(istype(I, /obj/item/tcg_card)) + var/obj/item/tcg_card/second_card = I + if(loc == user && second_card.loc == user) + var/obj/item/tcgcard_hand/hand = new(get_turf(user)) + src.forceMove(hand) + second_card.forceMove(hand) + hand.cards.Add(src) + hand.cards.Add(second_card) + user.put_in_hands(hand) + hand.update_icon() + return ..() + var/obj/item/tcgcard_deck/new_deck = new /obj/item/tcgcard_deck(drop_location()) + new_deck.flipped = flipped + user.transferItemToLoc(second_card, new_deck)//Start a new pile with both cards, in the order of card placement. + user.transferItemToLoc(src, new_deck) + new_deck.update_icon_state() + new_deck.update_icon() + if(istype(I, /obj/item/tcgcard_deck)) + var/obj/item/tcgcard_deck/old_deck = I + if(length(old_deck.contents) >= 30) + to_chat(user, "This pile has too many cards for a regular deck!") + return + user.transferItemToLoc(src, old_deck) + flipped = old_deck.flipped + old_deck.update_icon() + update_icon() + return ..() + +/obj/item/tcg_card/attack_self(mob/user) + var/list/possible_actions = list( + "Reset to Default" = image(icon = 'icons/mob/radial.dmi', icon_state = "radial_reset"), + "Change stats" = image(icon = 'icons/mob/radial.dmi', icon_state = "radial_change_stats") + ) + var/result = show_radial_menu(user, src, possible_actions, require_near = TRUE, tooltips = TRUE) + switch(result) + if("Reset to Default") + card_datum.Reset(user) + user.visible_message("[user] resets [src]'s stats.") + if("Change stats") + possible_actions = list( + "Health" = image(icon = 'icons/mob/radial.dmi', icon_state = "radial_health"), + "Attack" = image(icon = 'icons/mob/radial.dmi', icon_state = "radial_attack"), + "Mana" = image(icon = 'icons/mob/radial.dmi', icon_state = "radial_mana") + ) + result = show_radial_menu(user, src, possible_actions, require_near = TRUE, tooltips = TRUE) + switch(result) + if("Health") + card_datum.health = input(user, "What do you want health to be?", "Changing [src]'s health") as num|null + if("Attack") + card_datum.attack = input(user, "What do you want attack to be?", "Changing [src]'s attack") as num|null + if("Mana") + card_datum.mana_cost = input(user, "What do you want mana cost to be?", "Changing [src]'s mana cost") as num|null + user.visible_message("[user] changes [src]'s [result].") + +/obj/item/tcg_card/equipped(mob/user, slot, initial) + . = ..() + transform = matrix() + +/obj/item/tcg_card/dropped(mob/user, silent) + . = ..() + transform = matrix(0.5,0,0,0,0.5,0) + +/obj/item/cardpack + name = "Trading Card Pack: Coder" + desc = "Contains six complete fuckups by the coders. Report this on github please!" + icon = 'icons/obj/tcg/misc.dmi' + icon_state = "cardpack" + w_class = WEIGHT_CLASS_TINY + ///The card series to look in + var/list/series = list(/datum/tcg_card/pack_1, /datum/tcg_card/exodia) + ///Chance of the pack having a coin in it out of 10 + var/contains_coin = -1 + ///The amount of cards to draw from the rarity table + var/card_count = 5 + ///The rarity table, the set must contain at least one of each + var/list/rarity_table = list( + "Common" = 900, + "Rare" = 300, + "Epic" = 50, + "Legendary" = 3, + "Exodia" = 1) //Basically 0.1%, it doesn't have guar. rarity + ///The amount of cards to draw from the guarenteed rarity table + var/guaranteed_count = 1 + ///The guaranteed rarity table, acts about the same as the rarity table. it can have as many or as few raritys as you'd like + var/list/guar_rarity = list( + "Legendary" = 1, + "Epic" = 9, + "Rare" = 30) + + var/illegal = FALSE //Can cargo get it? + + custom_price = PRICE_EXPENSIVE + +/obj/item/cardpack/series_one + name = "Trading Card Pack: 2560 Core Set" + desc = "Contains six cards of varying rarity from the 2560 Core Set. Collect them all!" + icon_state = "cardpack" + series = list(/datum/tcg_card/pack_1, /datum/tcg_card/exodia) + contains_coin = 10 + +/obj/item/cardpack/syndicate //More cards. Perfect stuff for gaming gang + name = "Trading Card Pack: Nuclear Danger" + desc = "Contains twelve cards of varying rarity from 2560 Core Set and 2560 Nuclear Danger. This pack was stamped by Waffle Co." + icon_state = "cardpack_syndicate" + series = list(/datum/tcg_card/pack_1, /datum/tcg_card/pack_nuclear) + contains_coin = 100 + + card_count = 9 + guaranteed_count = 3 + + illegal = TRUE + + guar_rarity = list( //Better chances + "Legendary" = 5, + "Epic" = 10, + "Rare" = 30) + +/obj/item/cardpack/equipped(mob/user, slot, initial) + . = ..() + transform = matrix() + +/obj/item/cardpack/dropped(mob/user, silent) + . = ..() + transform = matrix(0.5,0,0,0,0.5,0) + +/obj/item/cardpack/attack_self(mob/user) + . = ..() + var/list/cards = buildCardListWithRarity(card_count, guaranteed_count) + var/obj/item/tcgcard_hand/hand = new(get_turf(user)) + for(var/template in cards) + var/obj/item/tcg_card/card = new(hand, template, illegal) + hand.cards.Add(card) + user.put_in_hands(hand) + hand.update_icon() + to_chat(user, "Wow! Check out these cards!
    ") + playsound(loc, 'sound/items/poster_ripped.ogg', 20, TRUE) + if(prob(contains_coin)) + to_chat(user, "...and it came with a flipper, too!") + new /obj/item/coin/thunderdome(get_turf(user)) + new /obj/item/paper/tcg_rules(get_turf(user)) + qdel(src) + +/obj/item/cardpack/proc/buildCardListWithRarity(card_cnt, rarity_cnt) + var/list/return_cards = list() + + var/list/cards = list() + for(var/card_type in series) + for(var/card in subtypesof(card_type)) + var/datum/tcg_card/new_card = new card() + if(new_card.name == "Stupid Coder") + continue + cards.Add(card) + qdel(new_card) + var/list/possible_cards = list() + var/list/rarity_cards = list("Exodia" = list(), "Legendary" = list(), "Epic" = list(), "Rare" = list(), "Common" = list()) + for(var/card in cards) + var/datum/tcg_card/new_card = new card() + if(new_card.name == "Stupid Coder") + continue + possible_cards[card] = rarity_table[new_card.rarity] + var/list/rarity_card_type = rarity_cards[new_card.rarity] + if(!rarity_card_type) + rarity_card_type = list() + rarity_card_type.Add(card) + rarity_cards[new_card.rarity] = rarity_card_type //FUCK CI + qdel(new_card) + + for(var/card_counter = 1 to card_count) + var/cardtype = pickweight(possible_cards) + return_cards.Add(cardtype) + + for(var/card_counter = 1 to guaranteed_count) + var/card_list = pickweight(guar_rarity) + return_cards.Add(pick(rarity_cards[card_list])) + + return return_cards + +/obj/item/coin/thunderdome + name = "Thunderdome Flipper" + desc = "A Thunderdome TCG flipper, for deciding who gets to go first. Also conveniently acts as a counter, for various purposes." + icon = 'icons/obj/tcg/misc.dmi' + icon_state = "coin_nanotrasen" + custom_materials = list(/datum/material/plastic = 400) + material_flags = NONE + sideslist = list("nanotrasen", "syndicate") + +/obj/item/coin/thunderdome/Initialize() + . = ..() + transform = matrix(0.5,0,0,0,0.5,0) + +/obj/item/coin/thunderdome/equipped(mob/user, slot, initial) + . = ..() + transform = matrix() + +/obj/item/coin/thunderdome/dropped(mob/user, silent) + . = ..() + transform = matrix(0.5,0,0,0,0.5,0) + +/obj/item/tcgcard_deck + name = "Trading Card Pile" + desc = "A stack of TCG cards." + icon = 'icons/obj/tcg/misc.dmi' + icon_state = "deck_up" + + var/flipped = FALSE + + var/static/radial_draw = image(icon = 'icons/mob/radial.dmi', icon_state = "radial_draw") + var/static/radial_shuffle = image(icon = 'icons/mob/radial.dmi', icon_state = "radial_shuffle") + var/static/radial_pickup = image(icon = 'icons/mob/radial.dmi', icon_state = "radial_pickup") + +/obj/item/tcgcard_deck/Initialize() + . = ..() + LoadComponent(/datum/component/storage/concrete/tcg) + +/obj/item/tcgcard_deck/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage/concrete/tcg) + STR.storage_flags = STORAGE_FLAGS_LEGACY_DEFAULT + STR.max_volume = DEFAULT_VOLUME_TINY * 30 + STR.max_w_class = DEFAULT_VOLUME_TINY + STR.max_items = 30 + +/obj/item/tcgcard_deck/update_icon_state() + . = ..() + if(flipped) + switch(contents.len) + if(1 to 10) + icon_state = "deck_low" + if(11 to 20) + icon_state = "deck_half" + if(21 to INFINITY) + icon_state = "deck_full" + else + icon_state = "deck_up" + +/obj/item/tcgcard_deck/examine(mob/user) + . = ..() + . += "\The [src] has [contents.len] cards inside." + +/obj/item/tcgcard_deck/attack_hand(mob/user) + var/list/choices = list( + "Draw" = image(icon = 'icons/mob/radial.dmi', icon_state = "radial_draw"), + "Shuffle" = image(icon = 'icons/mob/radial.dmi', icon_state = "radial_shuffle"), + "Pickup" = image(icon = 'icons/mob/radial.dmi', icon_state = "radial_pickup"), + "Flip" = image(icon = 'icons/mob/radial.dmi', icon_state = "radial_flip"), + ) + var/choice = show_radial_menu(user, src, choices, custom_check = CALLBACK(src, .proc/check_menu, user), require_near = TRUE, tooltips = TRUE) + if(!check_menu(user)) + return + switch(choice) + if("Draw") + draw_card(user) + if("Shuffle") + shuffle_deck(user) + if("Pickup") + user.put_in_hands(src) + if("Flip") + flip_deck() + +/obj/item/tcgcard_deck/Destroy() + for(var/card in 1 to contents.len) + var/obj/item/tcg_card/stored_card = contents[card] + stored_card.forceMove(drop_location()) + . = ..() + +/obj/item/tcgcard_deck/proc/check_menu(mob/living/user) + if(!istype(user)) + return FALSE + if(user.incapacitated() || !user.Adjacent(src)) + return FALSE + return TRUE + +/obj/item/tcgcard_deck/attackby(obj/item/I, mob/living/user, params) + . = ..() + if(istype(I, /obj/item/tcg_card)) + if(contents.len > 30) + to_chat(user, "This pile has too many cards for a regular deck!") + return FALSE + var/obj/item/tcg_card/new_card = I + new_card.flipped = flipped + new_card.forceMove(src) + + if(istype(I, /obj/item/tcgcard_hand)) + var/obj/item/tcgcard_hand/hand = I + for(var/obj/item/tcg_card/card in hand.cards) + if(contents.len > 30) + return FALSE + card.flipped = flipped + card.forceMove(src) + hand.cards.Remove(card) + +/obj/item/tcgcard_deck/attack_self(mob/living/carbon/user) + shuffle_deck(user) + return ..() + +/obj/item/tcgcard_deck/proc/draw_card(mob/user) + if(!contents.len) + CRASH("A TCG deck was created with no cards inside of it.") + var/obj/item/tcg_card/drawn_card = contents[contents.len] + user.put_in_hands(drawn_card) + drawn_card.flipped = flipped //If it's a face down deck, it'll be drawn face down, if it's a face up pile you'll draw it face up. + drawn_card.update_icon_state() + user.visible_message("[user] draws a card from \the [src]!", \ + "You draw a card from \the [src]!") + if(contents.len <= 1) + var/obj/item/tcg_card/final_card = contents[1] + user.transferItemToLoc(final_card, drop_location()) + qdel(src) + +/obj/item/tcgcard_deck/proc/shuffle_deck(mob/user, visable = TRUE) + if(!contents) + return + contents = shuffle(contents) + if(user.active_storage) + user.active_storage.close(user) + if(visable) + user.visible_message("[user] shuffles \the [src]!", \ + "You shuffle \the [src]!") + +/obj/item/tcgcard_deck/proc/flip_deck() + flipped = !flipped + var/list/temp_deck = contents.Copy() + contents = reverseRange(temp_deck) + //Now flip the cards to their opposite positions. + for(var/a in 1 to contents.len) + var/obj/item/tcg_card/nu_card = contents[a] + nu_card.flipped = flipped + nu_card.update_icon_state() + update_icon_state() + +/obj/item/tcgcard_hand + name = "Trading Card Hand" + desc = "A hand full of TCG cards." + icon = 'icons/effects/effects.dmi' + icon_state = "nothing" + w_class = WEIGHT_CLASS_TINY + var/list/cards = list() + +/obj/item/tcgcard_hand/update_icon() + . = ..() + cut_overlays() + var/angular = length(cards) / 2 * -30 + 15 + for(var/obj/item/tcg_card/card in cards) + var/image/I = image(icon = card.icon, icon_state = card.icon_state) + var/matrix/ntransform = matrix(I.transform) + ntransform.TurnTo(angular, 0) + ntransform.Translate(sin(angular) * -15, cos(angular) * 15) + I.transform = ntransform + angular += 30 + overlays += I + +/obj/item/tcgcard_hand/attackby(obj/item/I, mob/living/user, params) + if(istype(I, /obj/item/tcg_card)) + var/obj/item/tcg_card/card = I + if(loc == user && card.loc == user) + card.forceMove(src) + cards.Add(card) + update_icon() + . = ..() + +/obj/item/tcgcard_hand/attack_hand(mob/living/carbon/user) + if(loc == user) + var/list/choices = list() + for(var/obj/item/tcg_card/card in cards) + choices[card] = image(icon = card.icon, icon_state = card.icon_state) + var/obj/item/tcg_card/choice = show_radial_menu(user, src, choices, require_near = TRUE, tooltips = TRUE) + if(choice) + choice.forceMove(get_turf(src)) + user.put_in_hands(choice) + cards.Remove(choice) + update_icon() + if(length(cards) == 0) + qdel(src) + return + . = ..() + +/obj/item/tcgcard_hand/equipped(mob/user, slot, initial) + . = ..() + transform = matrix() + +/obj/item/tcgcard_hand/dropped(mob/user, silent) + . = ..() + transform = matrix(0.5,0,0,0,0.5,0) + +/obj/item/tcgcard_binder + name = "Trading Card Binder" + desc = "A TCG-branded card binder, specifically for your infinite collection of TCG cards!" + icon = 'icons/obj/tcg/misc.dmi' + icon_state = "binder" + w_class = WEIGHT_CLASS_SMALL + + var/list/cards = list() + var/list/decks = list() + var/mode = 0 //If 1, will show all the cards even if you don't have em. If 2, will show your decks + +/obj/item/tcgcard_binder/attackby(obj/item/I, mob/living/user, params) + if(istype(I, /obj/item/tcg_card)) + var/obj/item/tcg_card/card = I + card.forceMove(src) + cards.Add(card) + if(istype(I, /obj/item/tcgcard_hand)) + var/obj/item/tcgcard_hand/hand = I + for(var/obj/item/tcg_card/card in hand.cards) + card.forceMove(src) + cards.Add(card) + qdel(I) + if(istype(I, /obj/item/tcgcard_deck)) + var/obj/item/tcgcard_deck/deck = I + var/named = input(user, "How will this deck be named? Leave this field empty if you don't want to save this deck.") + if(named) + decks[named] = list() + for(var/obj/item/tcg_card/card in deck.contents) + card.forceMove(src) + cards.Add(card) + if(named) + decks[named] += card.name + qdel(I) + . = ..() + +/obj/item/tcgcard_binder/attack_self(mob/living/carbon/user) + mode = (mode + 1) % 3 + switch(mode) + if(0) + to_chat(user, "[src] now shows you the cards you already have.") + if(1) + to_chat(user, "[src] now shows you all the different cards.") + if(2) + to_chat(user, "[src] now shows you your deck menu.") + +/obj/item/tcgcard_binder/attack_hand(mob/living/carbon/user) + if(loc == user) + var/list/choices = list() + switch(mode) + if(1) + var/card_types = list() + + for(var/obj/item/tcg_card/card in cards) + card_types[card.datum_type] = card + + for(var/card_type in subtypesof(/datum/tcg_card)) + if(card_type in card_types) + var/obj/item/tcg_card/card = card_types[card_type] + choices[card] = image(icon = card.icon, icon_state = card.icon_state) + continue + + var/datum/tcg_card/card_dat = new card_type + if(card_dat.name == "Stupid Coder") + continue + var/image/I = image(icon = card_dat.pack, icon_state = card_dat.icon_state) + I.color = "#999999" + choices[card_dat.name] = I + qdel(card_dat) + if(0) + for(var/obj/item/tcg_card/card in cards) + choices[card] = image(icon = card.icon, icon_state = card.icon_state) + + if(2) + for(var/deck in decks) + choices[deck] = image(icon = 'icons/obj/tcg/misc.dmi', icon_state = "deck_up") + + var/obj/item/tcg_card/choice = show_radial_menu(user, src, choices, require_near = TRUE, tooltips = TRUE) + if(choice && (choice in cards)) + choice.forceMove(get_turf(src)) + user.put_in_hands(choice) + cards.Remove(choice) + + if(choice && (choice in decks)) + var/obj/item/tcgcard_deck/new_deck = new(get_turf(user)) + var/list/required_cards = decks[choice] + for(var/obj/item/tcg_card/card in cards) + if(card.name in required_cards) + required_cards.Remove(card.name) + cards.Remove(card) + card.forceMove(new_deck) + user.put_in_hands(new_deck) + + if(choice) + return + . = ..() + +/obj/item/tcgcard_binder/proc/check_for_exodia() + var/list/card_types = list() + for(var/obj/item/tcg_card/card in cards) + card_types.Add(card.datum_type) + + for(var/card_type in subtypesof(/datum/tcg_card)) + var/datum/tcg_card/card_dat = new card_type + + if(card_dat.name == "Eldritch Horror" && (card_type in card_types)) //We already have Exodia saved + qdel(card_dat) + return + + if(card_dat.name == "Stupid Coder" || card_dat.name == "Eldritch Horror") //It would be stupid if we require exodia or system cards to get exodia + continue + qdel(card_dat) + if(!(card_type in card_types)) + return + + var/obj/item/tcg_card/card = new(get_turf(src), /datum/tcg_card/exodia/exodia) + card.forceMove(src) + cards.Add(card) + +/obj/item/tcgcard_binder/full/Initialize() //For admemes. + . = ..() + for(var/cardtype in subtypesof(/datum/tcg_card)) + var/obj/item/tcg_card/card = new(get_turf(src), cardtype) + if(card.card_datum.name == "Stupid Coder") + qdel(card) + continue + card.forceMove(src) + cards.Add(card) + +/obj/item/paper/tcg_rules + name = "TCG Rulebook" + desc = "A small rulebook containing a starter guide for TCG." + icon = 'icons/obj/tcg/misc.dmi' + icon_state = "deck_low" + w_class = WEIGHT_CLASS_TINY + + info = "*---------* \n\ + Welcome to the Exciting world of Tactical Card Game! Sponsored by Nanotrasen Edu-tainment Devision. \n \ + Core Rules: \n \ +
    \n \ + Tactical Card Game (Also known as TCG) is a traditional trading card game. It's played between two players, each with a deck or collection of cards. \n \ + +
    \n \ + Each player's deck contains up to 30 cards. Each player's hand can hold a maximum of 7 cards. At the end of your turn, if you have more than 7 cards, you must choose cards to discard to your discard pile until you have 7 cards. \n \ + To begin a match, both players must flip a coin to decide who goes first. The winner of the coin toss then decides if they go first or second. Before the match begins each player draws 5 cards each with the ability to mulligan cards from their hand facedown once (Basically, you get a first pass where you can replace cards in your hands back into your deck, shuffle your deck, then draw until you're back to 5). \n \ + Each player begins with 1 Max Mana to start with, which serves as the cost to playing cards. \n \ + +
    \n \ + In order to play the TCG, a deck is required. As stated above, decks must contain up to 30 cards. \n \ + Additionally, to save cards you need to have a card binder on yourself to store the cards. When the shift ends, your cards will be automatically saved by integrated scanners in your card binder. \n \ + Finally, a stock of Thunderdome Flippers to use for coin tosses and counter effects is recommended- these can be obtained occasionally from cardpacks, but any coin will do. \n \ + +
    \n \ + Win condition is simple - kill your opponent's hero by depleting all of their 20 lifeshards. \n \ + +
    \n \ + Gameplay Phases: \n \ + +
    \n \ + A single turn of the game goes as follows, and the order of card effects is very similar to other card games. Within a single turn, the following phases are gone through, in order, unless otherwise altered by a card effect. Turn Phases are the Draw Phase, Effect Phase 1, Play Phase, Combat Phase, Effect Phase 2, and the End Phase. \n \ + +
    \n \ + During the draw phase, the player whose turn it is untaps all their cards, then draws a single card. They gain 1 Max Mana, and their Mana is refilled. Cards with missing health due to defending, attacking, or damage effects return to max health at the end of the draw phase. \n \ + During the First Effect Phase, this is when effects that take place at the start of your turn would occur. If an opponent's effect takes place at the start of your turn, their effects will always take place first, then yours, unless otherwise stated by a card effect. If an opponent's effect would cause you to lose the game, and your effects would prevent that condition from happening afterwards, you would lose the game. As a general roll, when it's your turn, your opponent's effects take place FIRST, then yours. \n \ + +
    \n \ + During the Play Phase, this is when you can play, summon, or activate your own cards. Card Effects that don't state when they're activated MUST be activated during the Play Phase. Your opponent can also activate their own card effects in response to one of your actions during your play phase, if able. Any card played during the play phase can activate its effect as soon as it's played. More details within the Card Breakdown section. \n \ + +
    \n \ + During the Battle Phase, a Unit Card is able to battle other Unit Cards, or attack their opponent once per turn. Neither player can attack on their first turn, and all cards that enter the field can attack as soon as they can, unless it is that player's first turn, or they are prevented by a card effect. More details within the Card Combat section. \n \ + +
    \n \ + During the End Phase, end of turn effects will occur. If the active player has more than 7 cards in their hand by this point, this is when they must discard cards. All of the player's cards who used an effect at any point in the turn are refreshed, and able to use their effect again going into the opponent's turn. By the end of their turn, if the player has more than 7 cards, they must discard cards from their hand until 7 remain. \n \ + After all 5 phases have passed, the players turn officially ends, and the opponent begins their turn, starting anew from the draw phase. \n \ + +
    \n \ + Card effects are typically limited to the turn that that card is played. For example, a card effect that provides a card +1/+1 attack/health would only last until the end of the turn, unless otherwise stated, OR if the card is an Equipment Card. More on those below. \n \ + +
    \n \ + Card Breakdown: \n \ + +
    \n \ + Within the game, there are 3 kinds of cards (So far), Unit, Equipment and Spell cards. \n \ + +
    \n \ + Unit Cards. All Unit Cards have 4 core values to keep in mind, Attack, Health, Faction, and Summoning Cost. Attack serves as a card's offensive value in combat. Health serves as a card's defensive value in combat, and doubles as a card's health. Factions are groupings of cards that can often share effects and traits together. Summoning Cost is how much mana a card needs in order to be summoned. \n \ + +
    \n \ + Equipment Cards. All Equipment Cards similarly to Unit Cards have Attack, Health, and Summon Cost values, but for equipment, these values are added to the attached card's values. Equipment can only be attached (Equip) to units, and they last until the unit dies, or otherwise leaves the field, following it's equipt card. If returned to the hand, send to the discard pile, or otherwise leaves the field, it is detatched from the equipt card. When a Equipment Card increases a card's attack or health, those effects stay on the equip card until the equipment is unequip or removed from the parent card. \n \ + If a card would have it's health decreased by having it's equip card removed, it's handled by having it's maximum health decreased, not it's current health. For example, lets say you had a card with 1/1 attack/health, and give it an equipment giving it +1/+2, then that card enters combat, dropping it down to 2/1. If by an opponent's card effect it lost that +1/+2 equipment now, it's stats would be 1/1 once again. If an equip card explicitly lowers a card's stats, it is possible for a card to be killed as a result, but drops in attack will always bottom out at 0 attack at any given time. \n \ + +
    \n \ + Spell Cards. Spell Cards don't have attack or health values, instead, they activate their effects as soon as they are summoned and leave the field afterwards(if not stated otherwise). \n \ +
    \n \ + Card Subtypes: \n \ +
    \n \ + Card effects: \n \ + Asimov - Unit cannot attack units with Human subtype \n \ + Changeling - Unit posesses all the subtypes at the same time \n \ + Greytide - On summon, unit gains amount of power equal to amount of other units with Greytide for 1 turn \n \ + Holy - Unit can't be targeted by spells \n \ + Taunt - All opposing unit attacks must be directed towards the unit with Taunt. \n \ + First Strike - This unit attacks first. If attacked unit is dead, unit doesn't recieve damage from it. \n \ + Deadeye - This unit can always hit opponents, regardless of effects or immunities. \n \ + Squad Tactics - When this unit attacks an opponent's unit and defeats it in combat, the owner of the defeated card takes 1 lifeshard of damage from combat. \n \ + Immunity - The unit cannot be affected by card effects or combat of its immunity type. This includes both friendly and opposing effects. \n \ + Fury - The unit must attack at every possibility. \n \ + Blocker - The unit cannot declare attacks, but can defend. \n \ + Hivemind - The unit enters combat with a hivemind token on it. The first time this card would take damage, remove that token instead. This does not apply to instant removal effects, only points of damage. \n \ + Clockwork - The unit can copy a single keyword on another unit on the field, until they lose the clockwork keyword or leave the field. \n \ +
    \n \ + Card Combat: \n \ +
    \n \ + Card combat is determined as follows. On your turn, any non-tapped unit card with a positive attack power is capable of declaring an attack. Upon declaring an attack, you must state if you're attacking your opponent directly, or if you're going to attack a specific opponent's unit. Unless otherwise stated, cards can only attack or defend one time per turn. \n \ +
    \n \ + An attack against a unit healths as follows: Both units will do their power as damage to the opponent's unit's health. Damage is typically dealt at the same time, and if both units would kill each other through combat, both are destroyed at the same time. If One or both units would not be destroyed by combat, they would have their health reduced by the difference of their health minus their opponent's power, until the start of your next turn. If the attacker or defender has a keyword or effect that prevents them from attacking their opponent (Like silicon, immunity), then they are not able to attack, but may still defend against the opponent's attack. Once combat has healthd, all remaining participants become tapped. \n \ +
    \n \ + A direct attack healths as follows: The attacking unit declares an attack against the opponent's lifeshards. Your opponent may then declare a defender if one is available, who will then turn the combat into an attack against a unit for the purposes of combat that turn. If the attack is not blocked, and the direct attack connects, then your opponent loses a number of lifeshards equal to the attacking units power.
    " + +/obj/item/cardboard_card + name = "cardboard card cutout" + desc = "A small piece of cardboard shaped as a TCG card." + icon = 'icons/obj/tcg/misc.dmi' + icon_state = "template" + +/datum/reagent/card_powder/reaction_obj(obj/O, reac_volume) + if(istype(O, /obj/item/cardboard_card)) + var/list/possible_cards = list() + for(var/card_series in COMMON_SERIES) + for(var/card_type in subtypesof(card_series)) + var/datum/tcg_card/card = new card_type + if(card.rarity == rarity) + possible_cards.Add(card_type) + qdel(card) + if(length(possible_cards)) + new /obj/item/tcg_card(get_turf(O), pick(possible_cards), TRUE) + qdel(O) + + . = ..() + +/mob/living/carbon/human/proc/SaveTCGCards() + if(!client) + return + + var/obj/item/tcgcard_binder/binder = locate() in src + if(!binder) + var/obj/item/storage/backpack/back = locate() in src + binder = locate() in back + + if(!binder) + return + + var/list/card_types = list() + for(var/obj/item/tcg_card/card in binder.cards) + //if(!card.illegal) //Uncomment if you want to block syndie cards from saving + if(!(card.datum_type in card_types)) + card_types[card.datum_type] = card.illegal + else + if(islist(card_types[card.datum_type])) + card_types[card.datum_type] += card.illegal + else + card_types[card.datum_type] = list(card_types[card.datum_type], card.illegal) + + client.prefs.tcg_decks = binder.decks + client.prefs.tcg_cards = card_types + client.prefs.save_character(TRUE) + +#undef COMMON_SERIES +#undef TAPPED_ANGLE +#undef UNTAPPED_ANGLE diff --git a/code/modules/tcg/pack_1.dm b/code/modules/tcg/pack_1.dm new file mode 100644 index 0000000000..a3920fcdaa --- /dev/null +++ b/code/modules/tcg/pack_1.dm @@ -0,0 +1,1431 @@ +/datum/tcg_card/pack_1 + pack = 'icons/obj/tcg/pack_1.dmi' + +//COMMAND + +/datum/tcg_card/pack_1/captain + name = "Captain" + desc = "Nanotrasen hires a captain for every station. However, most of the time they just drink wishkey and secure the disk." + rules = "Human. Tap this card for 1 mana: inflict -1/-1 to an opposing creature card." + icon_state = "captain" + + mana_cost = 7 + attack = 5 + health = 5 + + faction = "Command" + rarity = "Epic" + card_type = "Unit" + +/datum/tcg_card/pack_1/captain_hardsuit + name = "Apadyne Technologies Mk.2 R.I.O.T. Suit (Captain's Version)" + desc = "A heavily customised Apadyne Technologies Mk.2 R.I.O.T. Suit, rebuilt and refitted to Nanotrasen's highest standards for issue to Station Captains." + rules = "On equip: Equipped unit gains +1/+1 for one turn" + icon_state = "captain_hardsuit" + + mana_cost = 3 + attack = -1 + health = 5 + + faction = "Command" + rarity = "Legendary" + card_type = "Equipment" + +/datum/tcg_card/pack_1/hop + name = "Head of Personnel" + desc = "The head of the Cargo and Service Departments, guardian of all access, and Ian's lovable, yet dumb, sidekick." + rules = "Human. Blocker. Once per turn: A friendly card of your choice attacks twice." + icon_state = "hop" + + mana_cost = 7 + attack = 4 + health = 3 + + faction = "Command" + rarity = "Rare" + card_type = "Unit" + +/datum/tcg_card/pack_1/ian_hop + name = "Head of Ians" + desc = "What can be better than a corgi? A corgi with all access and HoP's hat!" + rules = "On summon: Summon a Command unit for free." + icon_state = "hop_ian" + + mana_cost = 5 + attack = 0 + health = 4 + + faction = "Command" + rarity = "Epic" + card_type = "Unit" + +/datum/tcg_card/pack_1/cmo + name = "Chief Medical Officer" + desc = "Head of the medical department, the CMO is expected to maintain the standards of his underlings." + rules = "Human. Whenever a Medical unit gains power, it gains +1 more." + icon_state = "cmo" + + mana_cost = 5 + attack = 4 + health = 4 + + faction = "Command" + rarity = "Common" + card_type = "Unit" + +/datum/tcg_card/pack_1/cmo_suit + name = "DeForest Medical Corporation 'Lifesaver' Carapace" + desc = "An advanced voidsuit designed for emergency medical personnel. Features include a built-in medical HUD and advanced medical gauntlets." + rules = "Tap this card: Re-equip 'DeForest Medical Corporation 'Lifesaver' Carapace' on a different friendly creature" + icon_state = "cmo_hardsuit" + + mana_cost = 3 + attack = 1 + health = 3 + + faction = "Command" + rarity = "Rare" + card_type = "Equipment" + +/datum/tcg_card/pack_1/hos + name = "Head of Security" + desc = "Nanotrasen hires most heads of staff based on their qualifications as being amicable, good at conflict resolution, ability to handle high-stakes situations, humanity, and desire to learn. Heads of Security only need a highschool degree." + rules = "Human. All opponent's cards cost 1 more until Head Of Security is removed from the battlefield." + icon_state = "hos" + + mana_cost = 7 + attack = 4 + health = 4 + + faction = "Command" + rarity = "Rare" + card_type = "Unit" + +/datum/tcg_card/pack_1/hos_suit + name = "Apadyne Technologies 'Tyrant' Class Hardshell" + desc = "The distinctive shape of the Tyrant Class Hardshell is caused, in part, by the large amount of kevlar reinforcement and the ablative armour layer. Perhaps more importantly, it also looks rad." + rules = "Grant the equipped card Fury." + icon_state = "hos_hardsuit" + + mana_cost = 6 + attack = 4 + health = 2 + + faction = "Command" + rarity = "Epic" + card_type = "Equipment" + +/datum/tcg_card/pack_1/ce + name = "Chief Engineer" + desc = "The Chief Engineer is in charge of keeping the station powered and intact. Most of CE's usually fail this task." + rules = "Human. Protect a friendly card from one spell." + icon_state = "ce" + + mana_cost = 6 + attack = 3 + health = 6 + + faction = "Command" + rarity = "Rare" + card_type = "Unit" + +/datum/tcg_card/pack_1/ce_suit + name = "Nakamura Engineering R.I.G.Suit (Advanced)" + desc = "An updated version of Nakamura Engineering's R.I.G.Suit fitted with advanced radiation shielding and extra armour." + rules = "On equip: Equipped creature is protected from one spell." + icon_state = "ce_hardsuit" + + mana_cost = 3 + attack = 0 + health = 3 + + faction = "Command" + rarity = "Rare" + card_type = "Equipment" + +/datum/tcg_card/pack_1/rd + name = "Research Director" + desc = "The Research Director is the head of the Science Division and is responsible for shockingly directing research." + rules = "Human. All Science card activate their effects twice." + icon_state = "rd" + + mana_cost = 7 + attack = 2 + health = 5 + + faction = "Command" + rarity = "Common" + card_type = "Unit" + +/datum/tcg_card/pack_1/rd_suit + name = "Nakamura Engineering B.O.M.B.Suit" + desc = "The Nakamura Engineering B.O.M.B.Suit is an innovative combination of a R.I.G.Suit and a bomb suit perfect for toxins research." + rules = "Reduces all incoming damage for 1. Does not work if damage is lethal." + icon_state = "rd_hardsuit" + + mana_cost = 3 + attack = 0 + health = 0 + + faction = "Command" + rarity = "Rare" + card_type = "Equipment" + + +//COMMAND END + +//SILICONS + +/datum/tcg_card/pack_1/ai + name = "AI" + desc = "The latest generation of NT's top secret artificial intelligence project this time with actual human brains in a jar! Don't tell the press though." + rules = "Asimov. All silicon cards gain +1/0 while this creature is alive." + icon_state = "ai" + + mana_cost = 5 + attack = 3 + health = 6 + + faction = "Silicon" + rarity = "Epic" + card_type = "Unit" + +/datum/tcg_card/pack_1/pai + name = "Personal AI Device" + desc = "Personal AI Devices are able to take the form of many household pets to provide a homely sense of comfort and companionship to their owners." + rules = "Asimov. Taunt." + icon_state = "pai" + + mana_cost = 2 + attack = 1 + health = 1 + + faction = "Silicon" + rarity = "Common" + card_type = "Unit" + +/datum/tcg_card/pack_1/cyborg + name = "Cyborg" + desc = "Created as part of humanity's first foray into artificial intelligence the original cyborg models used organic parts in lieu of sophisticated artificial brains." + rules = "Asimov." + icon_state = "borg_basic" + + mana_cost = 2 + attack = 3 + health = 3 + + faction = "Silicon" + rarity = "Common" + card_type = "Unit" + +/datum/tcg_card/pack_1/cyborg_clown + name = "Cyborg (Clown Shell)" + desc = "The clown shell is a new development in cyborg technology designed to capture the joyous hijinks of the station clown in a notably more macabre and disturbing fashion." + rules = "Asimov. Taunt." + icon_state = "borg_clown" + + mana_cost = 2 + attack = 2 + health = 4 + + faction = "Silicon" + rarity = "Common" + card_type = "Unit" + +/datum/tcg_card/pack_1/cyborg_engi + name = "Cyborg (Engineering Shell)" + desc = "A common sight on Nanotrasen Stations Engineering Shells maintain critical station systems in hazardous conditions." + rules = "Asimov." + icon_state = "borg_engi" + + mana_cost = 2 + attack = 4 + health = 2 + + faction = "Silicon" + rarity = "Rare" + card_type = "Unit" + +/datum/tcg_card/pack_1/cyborg_sec + name = "Cyborg (Security Shell)" + desc = "Following an incident in 2554 the Security Cyborg Shell was unilaterally phased out and replaced by the Peacekeeper. Nonetheless many units remain in service with various other organisations such as private militaries." + rules = "Asimov. Can attack humans, but deals only 1 damage." + icon_state = "borg_sec" + + mana_cost = 6 + attack = 4 + health = 2 + + faction = "Silicon" + rarity = "Rare" + card_type = "Unit" + +/datum/tcg_card/pack_1/cyborg_sec + name = "Cyborg (Peacekeeper Shell)" + desc = "After the unilateral phasing out of Security Shells in 2554 following mass reports of cyborg-on-human violence the Peacekeeper Shell was introduced as a stopgap solution until the problems could be resolved." + rules = "Asimov. Tap this card: Restore 2 health for a friendly creature." + icon_state = "borg_peace" + + mana_cost = 2 + attack = 4 + health = 3 + + faction = "Silicon" + rarity = "Common" + card_type = "Unit" + +/datum/tcg_card/pack_1/cyborg_med + name = "Cyborg (Medical Shell)" + desc = "A state of the art medical shell for when biological life just can't take care of itself. Comes equipped with built-in surgical equipment and all the medicated lollipops you could ever want." + rules = "Asimov. Loses 1 power for every Human on opponent's field." + icon_state = "borg_med" + + mana_cost = 2 + attack = 4 + health = 3 + + faction = "Silicon" + rarity = "Common" + card_type = "Unit" + +/datum/tcg_card/pack_1/cyborg_service + name = "Cyborg (Service Shell)" + desc = "Sometimes a cyborg just needs to show a bit of flamboyance you know?" + rules = "Asimov. Gains +2/+2 when it's the only card on your field." + icon_state = "borg_service" + + mana_cost = 1 + attack = 0 + health = 1 + + faction = "Silicon" + rarity = "Common" + card_type = "Unit" + +/datum/tcg_card/pack_1/cyborg_janitor + name = "Cyborg (Custodial Shell)" + desc = "A powerful state of the act cleaning machine. They exist to eradicate stains snag garbage and replace lights forever. We are legally obligated by the Janitor's Union to state that these machines are no replacement for a flesh-and-blood janitor." + rules = "Asimov. After tapping this card, tap an opponent's Human card as well." + icon_state = "borg_janitor" + + mana_cost = 2 + attack = 1 + health = 3 + + faction = "Silicon" + rarity = "Common" + card_type = "Unit" + +/datum/tcg_card/pack_1/cyborg_miner + name = "Cyborg (Mining Shell)" + desc = "Fitted with a drill and tracks the Mining Shell is designed to hold up to the rigours of mining be that on the hellish surface of Indecipheres or in the silent vacuum of the asteroid belt." + rules = "Asimov. Gain 1 additional mana every turn." + icon_state = "borg_miner" + + mana_cost = 2 + attack = 3 + health = 1 + + faction = "Silicon" + rarity = "Rare" + card_type = "Unit" + +//SILICONS END + +//CIVILIANS + +/datum/tcg_card/pack_1/assistant + name = "Assistant" + desc = "The lowest ladder on the Nanotrasen Employment Ladder, Assistants are employed to help out with tasks deemed 'too menial for robots'." + rules = "Greytide." + icon_state = "assistant" + + mana_cost = 1 + attack = 1 + health = 1 + + faction = "Civilian" + rarity = "Common" + card_type = "Unit" + +/datum/tcg_card/pack_1/greytider + name = "Greytider" + desc = "The lowest ladder on the Nanotrasen Employment Ladder, Assistants are employed to help out with tasks deemed 'too menial for robots'." + rules = "Greytide. Instead of getting +1/+1 on the first turn, get it permanently." + icon_state = "greytider" + + mana_cost = 1 + attack = 2 + health = 1 + + faction = "Civilian" + rarity = "Rare" + card_type = "Unit" + +/datum/tcg_card/pack_1/bartender + name = "Bartender" + desc = "Prior to the introduction of on-station psychologists the Bartender served to alleviate many employees' woes and fears. Remember always drink responsibly." + rules = "" + icon_state = "bartender" + + mana_cost = 3 + attack = 3 + health = 2 + + faction = "Civilian" + rarity = "Common" + card_type = "Unit" + +/datum/tcg_card/pack_1/botanist + name = "Botanist" + desc = "The Botanist is in charge of keeping the station's food supply happy healthy and preferably not laced with hallucinogens." + rules = "" + icon_state = "botanist" + + mana_cost = 1 + attack = 1 + health = 4 + + faction = "Civilian" + rarity = "Common" + card_type = "Unit" + +/datum/tcg_card/pack_1/botanist + name = "Botanist" + desc = "The Botanist is in charge of keeping the station's food supply happy healthy and preferably not laced with hallucinogens." + rules = "" + icon_state = "botanist" + + mana_cost = 1 + attack = 1 + health = 4 + + faction = "Civilian" + rarity = "Common" + card_type = "Unit" + +/datum/tcg_card/pack_1/chaplain + name = "Chaplain" + desc = "Every station should have it's own chaplain for religious purposes. Keyword is 'Should'." + rules = "Holy" + icon_state = "chaplain" + + mana_cost = 2 + attack = 2 + health = 3 + + faction = "Civilian" + rarity = "Common" + card_type = "Unit" + +/datum/tcg_card/pack_1/inquisitor + name = "Inquisitor's Hardsuit" + desc = "Nanotrasen officially doesn't believe in ghosts magic or anything that can't be solved with science. When you see someone show up in one of these let that remind you of that fact." + rules = "Holy. First Strike." + icon_state = "inquisitor" + + mana_cost = 4 + attack = 2 + health = 2 + + faction = "Civilian" + rarity = "Epic" + card_type = "Equipment" + +/datum/tcg_card/pack_1/janitor + name = "Janitor" + desc = "A true testament to futility they clean and they clean and they clean knowing that there's no way they can clean it all. Yet they perservere knowing that without them the crew would simply give in to their base animalistic nature." + rules = "Taunt" + icon_state = "janitor" + + mana_cost = 1 + attack = 1 + health = 1 + + faction = "Civilian" + rarity = "Common" + card_type = "Unit" + +/datum/tcg_card/pack_1/lawyer + name = "Lawyer" + desc = "Nanotrasen knows the value of a good lawyer. That's why they're all working hard at our home offices defending us from frivolous labor suits from lazy no-good employees who should be working hard instead of slacking off reading trading cards." + rules = "When an opponent attacks with a creature with 3 or more power this card gains Taunt." + icon_state = "lawyer" + + mana_cost = 2 + attack = 0 + health = 4 + + faction = "Civilian" + rarity = "Common" + card_type = "Unit" + +/datum/tcg_card/pack_1/clown + name = "Clown" + desc = "Every Nanotrasen station has a clown on board as high command believes that a source of entertainment will reduce instances of murder-suicide on board Spinward Stations. The results of this hypothesis are as of yet unproven." + rules = "Taunt. When killed, attacking creature dies as well" + icon_state = "clown" + + mana_cost = 3 + attack = 2 + health = 4 + + faction = "Civilian" + rarity = "Rare" + card_type = "Unit" + +/datum/tcg_card/pack_1/clown_hardsuit + name = "HONK Ltd. Entertainment Voidsuit" + desc = "The most advanced clown suit produced by HONK Ltd. the Entertainment Voidsuit is designed to withstand extreme conditions while still maintaining the aesthetic expected of clowns." + rules = "Give the equipped unit Taunt." + icon_state = "clown_hardsuit" + + mana_cost = 2 + attack = 1 + health = 5 + + faction = "Civilian" + rarity = "Legendary" + card_type = "Equipment" + +/datum/tcg_card/pack_1/mime + name = "Mime" + desc = "Si vous regardez attentivement dans les yeux d'un mime vous pouvez voir le tourment sans fin derrière leur façade silencieuse. C'est vraiment tragique." + rules = "Tap this card: Pick an opponent's card and nullify it's effect until it leaves play." + icon_state = "mime" + + mana_cost = 1 + attack = 2 + health = 1 + + faction = "Civilian" + rarity = "Rare" + card_type = "Unit" + +/datum/tcg_card/pack_1/cook + name = "Cook" + desc = "Every Nanotrasen chef is trained in 3 cuisines of their choosing upon being hired alongside the closely guarded secret of Close Quarters Cooking." + rules = "First Strike. When attacked, gain +1/0." + icon_state = "cook" + + mana_cost = 3 + attack = 3 + health = 2 + + faction = "Civilian" + rarity = "Common" + card_type = "Unit" + +/datum/tcg_card/pack_1/curator + name = "Curator" + desc = "In Nanotrasen polls the Curator has ranked as the most pointless job on station much to the ire of the Curator's union. Thankfully we don't have to listen to them." + rules = "On Summon: Draw a card. If it's a spell, discard it." + icon_state = "curator" + + mana_cost = 2 + attack = 1 + health = 1 + + faction = "Civilian" + rarity = "Common" + card_type = "Unit" + +/datum/tcg_card/pack_1/ian + name = "Ian" + desc = "This adorable corgi has become the defacto mascot of the Spinward Stations to many. He comes in many forms many sizes and many shapes but he's still just as lovable. Hand wash only." + rules = "Holy. Taunt." + icon_state = "ian" + + mana_cost = 3 + attack = 0 + health = 2 + + faction = "Civilian" + rarity = "Rare" + card_type = "Unit" + +//CIVILIAN END + +//SECURITY + +/datum/tcg_card/pack_1/sec_officer + name = "Security Officer" + desc = "Nanotrasen would like to remind all employees to support their station security team; remember the boys in red keep you safe!" + rules = "Squad Tactics." + icon_state = "officer" + + mana_cost = 3 + attack = 2 + health = 2 + + faction = "Security" + rarity = "Common" + card_type = "Unit" + +/datum/tcg_card/pack_1/warden + name = "Warden" + desc = "The Warden is tasked with the herculean (and futile) feat of defending the armory and brig and never leaving his post no matter the situation." + rules = "Squad Tactics. Blocker." + icon_state = "warden" + + mana_cost = 4 + attack = 2 + health = 4 + + faction = "Security" + rarity = "Rare" + card_type = "Unit" + +/datum/tcg_card/pack_1/detective + name = "Security Officer" + desc = "Nanotrasen hires nothing but the best detectives to investigate crime on our stations. A penchant for cigarettes and outdated fashion isn't mandatory but is appreciated." + rules = "Deadeye." + icon_state = "detective" + + mana_cost = 5 + attack = 3 + health = 2 + + faction = "Security" + rarity = "Common" + card_type = "Unit" + +/datum/tcg_card/pack_1/officer_ethereal + name = "Security Officer(Ethereal)" + desc = "A trained officer with BlueShift equipment. Wait, is he a red boy or a blue boy?" + rules = "Squad Tactics. On summon: This character can't be attacked for the first turn." + icon_state = "officer_ethereal" + + mana_cost = 6 + attack = 4 + health = 4 + + faction = "Security" + rarity = "Rare" + card_type = "Unit" + +//SECURITY END + +//RESEARCH AND DEVELOPMENT + +/datum/tcg_card/pack_1/scientist + name = "Scientist" + desc = "Rumours that Nanotrasen hires 'mad scientists' are greatly exaggerated. Scientists are regularly screened to ensure that their insanity remains within acceptable limits." + rules = "When this card is targeted by an opponent's single target spell you gain 1 lifeshard." + icon_state = "scientist" + + mana_cost = 4 + attack = 1 + health = 2 + + faction = "Research" + rarity = "Common" + card_type = "Unit" + +/datum/tcg_card/pack_1/scientist_moth + name = "Scientist(Moth)" + desc = "Moths are a common sight in Nanotrasen research departments acting as integral ideas guys for new clothing designs and lighting innovations." + rules = "" + icon_state = "scientist_moth" + + mana_cost = 1 + attack = 2 + health = 2 + + faction = "Research" + rarity = "Common" + card_type = "Unit" + +/datum/tcg_card/pack_1/roboticist + name = "Roboticist" + desc = "The roboticist's work is as close as Nanotrasen legally allows its employees to come to necromancy." + rules = "If a Asimov card on your side of the field is destroyed you may pay 2 mana and tap this card: Return that card to your hand." + icon_state = "roboticist" + + mana_cost = 3 + attack = 2 + health = 2 + + faction = "Research" + rarity = "Rare" + card_type = "Unit" + +/datum/tcg_card/pack_1/monkey + name = "Monkey" + desc = "Nanotrasen seeks to phase out animal testing by 2570 in accordance with new TerraGov legislation. This will be replaced with more ethical solutions such as computer simulations or experimentation on Assistants." + rules = "Greytide. This card is considered Human with a Geneticist on your side of the field." + icon_state = "monkey" + + mana_cost = 1 + attack = 1 + health = 1 + + faction = "Research" + rarity = "Common" + card_type = "Unit" + +/datum/tcg_card/pack_1/geneticist + name = "Geneticist" + desc = "Geneticists are tasked with manipulating human DNA to produce special effects. Nanotrasen maintains a strict 'no superhero' policy for mutations following the Superhero Civil War of 2150." + rules = "Tap this card and pay 3 mana: Give a friendly creature Human until this card leaves the field." + icon_state = "geneticist" + + mana_cost = 3 + attack = 3 + health = 4 + + faction = "Research" + rarity = "Common" + card_type = "Unit" + +/datum/tcg_card/pack_1/borgi + name = "Borgi Ian" + desc = "While Ian's cyborg costume is very convincing we at the NTED would like to remind all employees that Ian has not been experimented on." + rules = "Asimov. You may sacrifice this card in play: Summon a Silicon type card from your hand worth up to double this card's cost." + icon_state = "ian_robot" + + mana_cost = 2 + attack = 0 + health = 3 + + faction = "Research" + rarity = "Rare" + card_type = "Unit" + +//SCIENCE END + +//MEDICAL + +/datum/tcg_card/pack_1/doctor + name = "Medical Doctor" + desc = "Nanotrasen's doctors are well known for their ability to treat almost any ailment known to mankind... as well as causing a fair few in the process." + rules = "Tap this card: Select a card that has less attack than this card from your graveyard and summon it to your side of the field." + icon_state = "doctor" + + mana_cost = 3 + attack = 2 + health = 3 + + faction = "Medical" + rarity = "Common" + card_type = "Unit" + +/datum/tcg_card/pack_1/runtime + name = "Runtime" + desc = "Runtime is the CMO's personal feline companion and is well known for her laziness. It's said that opening a tin of tuna anywhere on the station will bring her running." + rules = "You may sacrifice this card: reduce the cost of summoning a Medical card this turn by 2 mana." + icon_state = "runtime" + + mana_cost = 3 + attack = 0 + health = 1 + + faction = "Medical" + rarity = "Rare" + card_type = "Unit" + +/datum/tcg_card/pack_1/chemist + name = "Chemist" + desc = "Chemists are encouraged to not set up illicit methamphetamine factories on the company's dime." + rules = "Tap this card: flip a coin. If heads: a friendly Medical card gains 0/+2. If tails an opponents unit of your choice gains +2/0." + icon_state = "chemist" + + mana_cost = 2 + attack = 0 + health = 3 + + faction = "Medical" + rarity = "Rare" + card_type = "Unit" + +/datum/tcg_card/pack_1/virologist + name = "Virologist" + desc = "Officially the virologist is present on station to deal with novel diseases and ailments that originate from deep space. As everyone knows this is not what the virologist actually does." + rules = "" + icon_state = "virologist" + + mana_cost = 3 + attack = 5 + health = 1 + + faction = "Medical" + rarity = "Common" + card_type = "Unit" + +/datum/tcg_card/pack_1/paramedic + name = "Paramedic" + desc = "Nanotrasen encourages all paramedics to think of others before themselves- if this means running through a plasma fire to save a colleague so be it." + rules = "Taunt, First Strike" + icon_state = "paramedic" + + mana_cost = 3 + attack = 2 + health = 3 + + faction = "Medical" + rarity = "Common" + card_type = "Unit" + +//MEDICAL END + +//ENGINEERING + +/datum/tcg_card/pack_1/engineer + name = "Station Engineer" + desc = "Station Engineers maintain the intricate and delicate web of machinery that keeps you and everyone else aboard your station alive. No pressure there then." + rules = "Tap this card: Reduce the first hit taken by an ally to zero." + icon_state = "engineer" + + mana_cost = 4 + attack = 2 + health = 2 + + faction = "Engineering" + rarity = "Common" + card_type = "Unit" + +/datum/tcg_card/pack_1/engi_hardsuit + name = "Nakamura Engineering's R.I.G.Suit" + desc = "Nakamura Engineering's R.I.G. is a hardsuit, specifically designed for engineers working in hostile enviroments. It features good armor and is rad-proof." + rules = "" + icon_state = "engineer_hardsuit" + + mana_cost = 2 + attack = 0 + health = 3 + + faction = "Engineering" + rarity = "Common" + card_type = "Equipment" + +/datum/tcg_card/pack_1/engineer_plasmaman + name = "Station Engineer (Plasmaman)" + desc = "The ever industrious plasmamen are well suited to engineering work due to their natural radiation resistance." + rules = "Immune to all spells except Security and Syndicate ones." + icon_state = "engineer_plasmeme" + + mana_cost = 5 + attack = 2 + health = 4 + + faction = "Engineering" + rarity = "Rare" + card_type = "Unit" + +/datum/tcg_card/pack_1/atmos_tech + name = "Atmospheric Technician" + desc = "The Atmospheric Technicians are tasked with keeping the station's air clean breathable and most importantly devoid of plasma." + rules = "On Summon: Search your deck for an Engineering Spell card and add it to your hand. Shuffle your deck afterward." + icon_state = "atmos_tech" + + mana_cost = 4 + attack = 2 + health = 3 + + faction = "Engineering" + rarity = "Common" + card_type = "Unit" + +/datum/tcg_card/pack_1/atmos_hardsuit + name = "Nakamura Atmospherics's R.I.G.Suit" + desc = "Nakamura Atmospherics's R.I.G. is just an old modified Engineering R.I.G.Suit that lacks rad-protection. Some technicans painted it blue and now it's 'fireproof'." + rules = "Equipped creature gains immunity to engineering spells." + icon_state = "atmos_tech_hardsuit" + + mana_cost = 2 + attack = 0 + health = 2 + + faction = "Engineering" + rarity = "Rare" + card_type = "Equipment" + +//ENGINEERING END + +//CARGO + +/datum/tcg_card/pack_1/cargo_tech + name = "Cargo Technician" + desc = "The grunts of Cargo. Any reports that Cargo Technicians are frequently overcome by revolutionary fervour are exaggerated." + rules = "Once per turn: Give this card -1/0 and gain 1 mana." + icon_state = "cargo_tech" + + mana_cost = 2 + attack = 3 + health = 1 + + faction = "Cargo" + rarity = "Common" + card_type = "Unit" + +/datum/tcg_card/pack_1/shaft_miner + name = "Shaft Miner" + desc = "When the station needs materials these are the guys who risk their lives bravely pioneering the wastes of Indecipheres to bring them in." + rules = "Tap this card: Draw one card. If it's not a spell, discard it." + icon_state = "miner" + + mana_cost = 6 + attack = 6 + health = 4 + + faction = "Cargo" + rarity = "Rare" + card_type = "Unit" + +/datum/tcg_card/pack_1/citrus + name = "Citrus" + desc = "Cargo's happy sloth pal. Known for his cute sweater and always getting in the way." + rules = "Taunt. Tap this card: Tap an opponent's card until the start of your next turn." + icon_state = "citrus" + + mana_cost = 2 + attack = 0 + health = 3 + + faction = "Cargo" + rarity = "Common" + card_type = "Unit" + +/datum/tcg_card/pack_1/quartermaster + name = "Quartermaster" + desc = "Every Nanotrasen station has a Quartermaster who controls the flow of cargo to and from the station and by extension to and from the hands of the crew. He's not given the distinction of being a head though. His job isn't hard enough." + rules = "Permanently tap this card. All cargo cards on your side gain +2/+2 until this card leaves the play." + icon_state = "quartermaster" + + mana_cost = 10 + attack = 4 + health = 4 + + faction = "Cargo" + rarity = "Rare" + card_type = "Unit" + +/datum/tcg_card/pack_1/explorer + name = "Explorer" + desc = "The Nanotrasen Explorers Corps boldly goes where humanity has never gone before. Or would if they weren't buried under mounds of bureaucracy." + rules = "Tap this card: Flip a coin if heads gain 4 mana this turn, if tails tap this card for 2 turns." + icon_state = "explorer" + + mana_cost = 2 + attack = 3 + health = 3 + + faction = "Cargo" + rarity = "Legendary" + card_type = "Unit" + +//CARGO END + +//CENTCOM + +/datum/tcg_card/pack_1/intern + name = "Intern" + desc = "All Nanotrasen interns come with 3 things: A resume a desire to learn and vague promises that they're getting paid at some point. So don't be too rough on them." + rules = "First Strike. Greytide." + icon_state = "intern" + + mana_cost = 1 + attack = 1 + health = 1 + + faction = "Centcom" + rarity = "Common" + card_type = "Unit" + +/datum/tcg_card/pack_1/ert_command + name = "NT P.A.V. Suit (Command)" + desc = "Issued to members of Emergency Response Teams the P.A.V. Suit gives superior protection from any threat the galaxy can throw at it. This particular model is outfitted with a sidearm holster and a sleek blue finish." + rules = "While equipped give the equipped unit Squad Tactics and First Strike." + icon_state = "ert_command" + + mana_cost = 2 + attack = 2 + health = 2 + + faction = "Centcom" + rarity = "Epic" + card_type = "Equipment" + +/datum/tcg_card/pack_1/ert_sec + name = "NT P.A.V. Suit (Security)" + desc = "Issued to members of Emergency Response Teams the P.A.V. Suit gives superior protection from any threat the galaxy can throw at it. This particular model is outfitted with bulletproof padding and an intimidating red finish." + rules = "While equipped give the equipped unit Squad Tactics." + icon_state = "ert_sec" + + mana_cost = 2 + attack = 2 + health = 1 + + faction = "Centcom" + rarity = "Rare" + card_type = "Equipment" + +/datum/tcg_card/pack_1/ert_med + name = "NT P.A.V. Suit (Medical)" + desc = "Issued to members of Emergency Response Teams the P.A.V. Suit gives superior protection from any threat the galaxy can throw at it. This particular model is outfitted with a sterile coating and a calming white finish." + rules = "While equipped give the equipped unit Squad Tactics." + icon_state = "ert_med" + + mana_cost = 2 + attack = 1 + health = 2 + + faction = "Centcom" + rarity = "Common" + card_type = "Equipment" + +/datum/tcg_card/pack_1/ert_engi + name = "NT P.A.V. Suit (Engineering)" + desc = "Issued to members of Emergency Response Teams the P.A.V. Suit gives superior protection from any threat the galaxy can throw at it. This particular model is outfitted with a welding screen and a flashy yellow finish." + rules = "While equipped give the equipped unit Squad Tactics." + icon_state = "ert_engi" + + mana_cost = 1 + attack = 1 + health = 2 + + faction = "Centcom" + rarity = "Common" + card_type = "Equipment" + +/datum/tcg_card/pack_1/deathsquad + name = "Deathsquad Officer" + desc = "There were rumors about 'Deathsquads' killing station where something horrible happened, but we remind you that's it's just a lie." + rules = "Taunt. First Strike." + icon_state = "deathsquad" + + mana_cost = 8 + attack = 8 + health = 6 + + faction = "Centcom" + rarity = "Epic" + card_type = "Unit" + +//CENTCOM END + +//ANTAGONISTS + +/datum/tcg_card/pack_1/changeling + name = "Armored Changeling" + desc = "The strange creatures known as changelings have been known to develop natural armour as a defense mechanism when in combat." + rules = "Changeling." + icon_state = "changeling" + + mana_cost = 6 + attack = 2 + health = 8 + + faction = "Syndicate" + rarity = "Rare" + card_type = "Unit" + +/datum/tcg_card/pack_1/chrono_legionare + name = "Chrono Legionare" + desc = "Currently in the earliest stages of development the Chrono Legionnaire project is expected to weaponise time itself." + rules = "If this card is destroyed or discarded flip 3 coins. If the result has 2 or more heads add this card back to your hand. Otherwise send it to your graveyard." + icon_state = "chrono_legionare" + + mana_cost = 4 + attack = 6 + health = 2 + + faction = "Syndicate" + rarity = "Epic" + card_type = "Unit" + +/datum/tcg_card/pack_1/abductor_armor + name = "Combat Abductor Armor" + desc = "Recovered from the strange alien species known as the Abductors this armour is made from an extremely tough yet flexible material that has been dubbed as Alien Alloy by researchers." + rules = "Give equipped unit immunity to spells for 3 turns. Unequipped after 3 turns." + icon_state = "abductor" + + mana_cost = 6 + attack = 1 + health = 2 + + faction = "Syndicate" + rarity = "Common" + card_type = "Equipment" + +/datum/tcg_card/pack_1/wizard + name = "Wizard" + desc = "A strange men(or golem) wearing blue robes. For some reason, he looks like a total nerd." + rules = "Flip a coin every turn. If tails, deal 2 damage to any enemy unit except Holy ones. If heads, deal 2 damage to self." + icon_state = "wizard" + + mana_cost = 8 + attack = 6 + health = 4 + + faction = "Syndicate" + rarity = "Rare" + card_type = "Unit" + +/datum/tcg_card/pack_1/abductor_armor + name = "Wizard Federation Standard Issue Hardsuit" + desc = "Seemingly reverse engineered from captured engineering hardsuits the iconic Wizard Federation Hardsuit is a spectacular melding of technology and magic." + rules = "On Equip: The equipped creature cannot attack targets with Holy." + icon_state = "wizard_hardsuit" + + mana_cost = 1 + attack = 3 + health = 1 + + faction = "Syndicate" + rarity = "Rare" + card_type = "Equipment" + +/datum/tcg_card/pack_1/swarmer + name = "Swarmer" + desc = "Leading researchers theorise that Swarmers were designed as some kind of vanguard for an alien invasion force which seemingly has never materialised." + rules = "Greytide." + icon_state = "swarmer" + + mana_cost = 1 + attack = 1 + health = 1 + + faction = "Syndicate" + rarity = "Common" + card_type = "Unit" + +/datum/tcg_card/pack_1/swarmer_beacon + name = "Swarmer Beacon" + desc = "A strange device that can construct swarmers." + rules = "Every turn: Draw a card. If it's a Swarmer, play it for free. Else, discard it." + icon_state = "swarmer_beacon" + + mana_cost = 4 + attack = 0 + health = 1 + + faction = "Syndicate" + rarity = "Epic" + card_type = "Unit" + +/datum/tcg_card/pack_1/nukie + name = "Nuclear Operative" + desc = "The frontline grunts of the syndicate army Nuclear Operatives are typically well trained and equipped for their grim duty." + rules = "Squad Tactics." + icon_state = "nukie" + + mana_cost = 4 + attack = 4 + health = 2 + + faction = "Syndicate" + rarity = "Rare" + card_type = "Unit" + +/datum/tcg_card/pack_1/nukie_elite + name = "Elite Syndicate Nuclear Stormtrooper" + desc = "The best of the best of the syndicate troops elite stormtroopers can be distinguished by their black armour. Shoot on sight ask questions later!" + rules = "Squad Tactics. Fury." + icon_state = "nukie_elite" + + mana_cost = 7 + attack = 5 + health = 5 + + faction = "Syndicate" + rarity = "Epic" + card_type = "Unit" + +/datum/tcg_card/pack_1/clockwork_cultist + name = "Ratvarian Clockwork Cuirass" + desc = "Fashioned from paranormally reinforced brass the Ratvar Cult's clockwork armour is as beautiful as it is heretical." + rules = "While equipped give the equipped unit Clockwork." + icon_state = "clockwork_cultist" + + mana_cost = 4 + attack = 2 + health = 2 + + faction = "Syndicate" + rarity = "Rare" + card_type = "Equipment" + +/datum/tcg_card/pack_1/revenant + name = "Revenant" + desc = "The revenant is a spirit of pure hatred kept alive by drawing the life force of its enemies." + rules = "When a unit on dies Revenant gains 1/0." + icon_state = "revenant" + + mana_cost = 3 + attack = 2 + health = 3 + + faction = "Syndicate" + rarity = "Common" + card_type = "Unit" + +/datum/tcg_card/pack_1/angry_slime + name = "Crazy Slime" + desc = "An agressive slime who seeks blood. You totally should extinguish him." + rules = "When attacking, search your deck for Crazy Slime and add it to your hand. Shuffle your deck afterwards." + icon_state = "angry_slime" + + mana_cost = 2 + attack = 1 + health = 1 + + faction = "Syndicate" + rarity = "Common" + card_type = "Unit" + +//ANTAGONISTS END + +//SPELLS + +/datum/tcg_card/pack_1/adrenals + name = "Adrenals" + desc = "A potent mixture of stimulants designed to enhance a soldier's ability in the field. Technically illegal in Terragov territory but since when has that stopped anyone?" + rules = "Grant +2/+1 to a friendly unit." + icon_state = "adrenals" + + mana_cost = 1 + + faction = "Medical" + rarity = "Common" + card_type = "Spell" + +/datum/tcg_card/pack_1/defib + name = "Defibrillator" + desc = "A device that allows to re-start hearts using electricity. It also can be used as a weapon!" + rules = "Resurrect a friendly unit with 1 HP." + icon_state = "defib" + + mana_cost = 4 + + faction = "Medical" + rarity = "Rare" + card_type = "Spell" + +/datum/tcg_card/pack_1/morphine + name = "Morphine" + desc = "A sedative chemical that puts everyone who uses it into sleep." + rules = "Tap an enemy card without activating it's effect for 1 turn." + icon_state = "morphine" + + mana_cost = 2 + + faction = "Medical" + rarity = "Common" + card_type = "Spell" + +/datum/tcg_card/pack_1/bluespace + name = "Bluespace Flux" + desc = "Despite being a revolutionary new technology bluespace still has some... kinks that need sorted out." + rules = "Active for 3 turns. Every player can pay 2 mana to draw an additional card from their deck." + icon_state = "bluespace" + + mana_cost = 5 + + faction = "Research" + rarity = "Common" + card_type = "Spell" + +/datum/tcg_card/pack_1/bag_of_holding + name = "Bag Of Greed" + desc = "BAG OF GREED ALLOWS ME TO DRAW TWO MORE CARDS. I WILL START MY TURN BY PLAYING BAG OF GREED WHICH ALLOWS ME TO DRAW TWO MORE CARDS. I WILL PLAY THE EVENT CARD BAG OF GREED WHICH ALLOWS ME TO DRAW TWO NEW CARDS." + rules = "Draw 2 cards from your deck." + icon_state = "bag_of_holding" + + mana_cost = 3 + + faction = "Research" + rarity = "Common" + card_type = "Spell" + +/datum/tcg_card/pack_1/malfunction + name = "Glitch in the System" + desc = "Even a meticulously maintained AI system will eventually develop errors. Many are benign but some may cause unforeseen problems..." + rules = "Remove Asimov from one of your cards." + icon_state = "malfunction" + + mana_cost = 1 + + faction = "Research" + rarity = "Common" + card_type = "Spell" + +/datum/tcg_card/pack_1/botanist_plant + name = "Comitted Botanist" + desc = "When you've grown the plants nurtured the plants and harvested the plants there's only one place to go from there... becoming the plant." + rules = "Only usable when Botanist is on the field. This turn all service cards cost 2 mana less(but not below 1)." + icon_state = "botanist_plant" + + mana_cost = 4 + + faction = "Service" + rarity = "Rare" + card_type = "Spell" + +/datum/tcg_card/pack_1/gaia + name = "Ambrosia Gaia" + desc = "If Ambrosia is the gold of Botany the rare Gaia variety is the platinum. Almost nobody has seen this illusive plant with their own eyes." + rules = "During the draw phase you may sacrifice Ambrosia Gaia to gain 3 mana." + icon_state = "gaia" + + mana_cost = 0 + + faction = "Service" + rarity = "Legendary" + card_type = "Spell" + +/datum/tcg_card/pack_1/deep_fryer + name = "Deep Fryer" + desc = "God bless the United States of Space America." + rules = "Destroy an opponent's equipment card." + icon_state = "deep_fryer" + + mana_cost = 2 + + faction = "Service" + rarity = "Common" + card_type = "Spell" + +/datum/tcg_card/pack_1/bepis + name = "B.E.P.I.S." + desc = "God bless the United States of Space America." + rules = "Flip a coin. If heads, gain 2 mana. If tails, lose 2 mana." + icon_state = "bepis" + + mana_cost = 0 + + faction = "Cargo" + rarity = "Common" + card_type = "Spell" + +/datum/tcg_card/pack_1/economy_crash + name = "Economy Crash" + desc = "So cargo sold 20 canisters of miasma and now the galactic economy is experiencing what's known as 'a catastrophic collapse'." + rules = "All cards cost 1 more mana to play." + icon_state = "economy_crash" + + mana_cost = 2 + + faction = "Cargo" + rarity = "Common" + card_type = "Spell" + +/datum/tcg_card/pack_1/additional_supplies + name = "Additional Supplies" + desc = "Well, cargonia ordered 10 crates of buckshots and slugs. Looks like we need to dispose of them quickly." + rules = "For 3 turns, you draw an additional page every turn." + icon_state = "additional_supplies" + + mana_cost = 3 + + faction = "Cargo" + rarity = "Rare" + card_type = "Spell" + +/datum/tcg_card/pack_1/bsa_barrage + name = "BSA Barrage" + desc = "The officers at Centcom are well known for their ability to hit targets extremely accurately with their bluespace artillery especially when stupid pictures show up at their fax machine." + rules = "Destroy an opponent's unit. Deal 2 damage to all units on the field." + icon_state = "bsa_barrage" + + mana_cost = 4 + + faction = "Security" + rarity = "Rare" + card_type = "Spell" + +/datum/tcg_card/pack_1/reeducation + name = "Re-education" + desc = "Nobody ever seems to return from re-education. Probably best not to question it." + rules = "Deal 4 damage to an enemy's unit." + icon_state = "re-education" + + mana_cost = 2 + + faction = "Security" + rarity = "Common" + card_type = "Spell" + +/datum/tcg_card/pack_1/just_losses + name = "Justifiable Casualties" + desc = "The beat is hell. Officers die. The strongest they live." + rules = "Sacrifice two friendly creatures from the battlefield then summon a creature from your hand at no mana cost." + icon_state = "just_losses" + + mana_cost = 2 + + faction = "Security" + rarity = "Common" + card_type = "Spell" + +/datum/tcg_card/pack_1/sleeping_carp + name = "Sleeping Carp" + desc = "Created by the long-extinct Carp Monks of Space Tibet the Sleeping Carp style has been kept alive by dedicated practitioners and even found its way into the Syndicate's training regime." + rules = "Give a friendly unit +3/+1. Draw an additional card every turn while they are alive." + icon_state = "sleeping_carp" + + mana_cost = 6 + + faction = "Syndicate" + rarity = "Epic" + card_type = "Spell" + +/datum/tcg_card/pack_1/tough_choices + name = "Tough Choices" + desc = "Every Nanotrasen employee will at some point be forced to make a tough choice. Make sure you make the right one!" + rules = "Draw the top three cards from your deck. Summon one at no cost and discard the other two." + icon_state = "tough_choices" + + mana_cost = 2 + + faction = "Syndicate" + rarity = "Common" + card_type = "Spell" + +/datum/tcg_card/pack_1/nuclear_explosion + name = "Nuclear Explosion" + desc = "The Gorlex Marauders are well known for their nuclear weapons and their nuke first second third and fourth policy with regards to deploying them." + rules = "Kill all units on the battlefield." + icon_state = "nuclear_explosion" + + mana_cost = 5 + + faction = "Syndicate" + rarity = "Rare" + card_type = "Spell" + +/datum/tcg_card/pack_1/inducer + name = "Inducer" + desc = "The inducer is a marvelous piece of tech allowing the recharging of an internal cell without opening a machine." + rules = "Pay 3 lifeshards: Gain 3 mana this turn." + icon_state = "inducer" + + mana_cost = 0 + + faction = "Engineering" + rarity = "Rare" + card_type = "Spell" + +/datum/tcg_card/pack_1/plasmafire + name = "Atmospherics Incident" + desc = "Accidents happen." + rules = "For 3 turns, add -1/-1 to every unit." + icon_state = "plasmafire" + + mana_cost = 3 + + faction = "Engineering" + rarity = "Rare" + card_type = "Spell" + +/datum/tcg_card/pack_1/supermatter + name = "Supermatter" + desc = "A glowing crystal, made of hyper-pressurised plasma, widely known for it's radiation production." + rules = "Destroy an enemy's unit." + icon_state = "supermatter" + + mana_cost = 4 + + faction = "Engineering" + rarity = "Rare" + card_type = "Spell" diff --git a/code/modules/tcg/pack_nuclear.dm b/code/modules/tcg/pack_nuclear.dm new file mode 100644 index 0000000000..96ec174a4b --- /dev/null +++ b/code/modules/tcg/pack_nuclear.dm @@ -0,0 +1,483 @@ +/datum/tcg_card/pack_nuclear + pack = 'icons/obj/tcg/pack_nuclear.dmi' + +/datum/tcg_card/pack_nuclear/cayenne + name = "Cayenne" + desc = "A failed Syndicate experiment in weaponized space carp technology, it now serves as a lovable mascot." + rules = "Only playable when there are other Syndicate units on the field." + icon_state = "cayenne" + + mana_cost = 4 + attack = 4 + health = 3 + + faction = "Syndicate" + rarity = "Rare" + card_type = "Unit" + +/datum/tcg_card/pack_nuclear/esword + name = "Energy Sword" + desc = "Hard-light sword that doesn't leave burns. Don't ask questions." + rules = "" + icon_state = "esword" + + mana_cost = 3 + attack = 2 + health = 0 + + faction = "Syndicate" + rarity = "Common" + card_type = "Equipment" + +/datum/tcg_card/pack_nuclear/stechkin + name = "Stechkin Pistol" + desc = "A small, easily concealable 10mm handgun. Has a threaded barrel for suppressors." + rules = "When equipping this card, flip it so opponent won't see it. Flip the card after the first attack." + icon_state = "stechkin" + + mana_cost = 2 + attack = 2 + health = 0 + + faction = "Syndicate" + rarity = "Common" + card_type = "Equipment" + +/datum/tcg_card/pack_nuclear/c20r + name = "C-20R SMG" + desc = "A bullpup two-round burst .45 SMG, designated 'C-20r'. Has a 'Scarborough Arms - Per falcis, per pravitas' buttstamp." + rules = "After attack, flip a coin. If heads, leave the weapon. If tails, unequip this card." + icon_state = "c20r" + + mana_cost = 4 + attack = 4 + health = 0 + + faction = "Syndicate" + rarity = "Rare" + card_type = "Equipment" + +/datum/tcg_card/pack_nuclear/l6saw + name = "L6 Saw LMG" + desc = "A heavily modified 1.95x129mm light machine gun, designated 'L6 SAW'. Has 'Aussec Armoury - 2531' engraved on the receiver below the designation." + rules = "After equipped unit dies, this card goes to the bottom of draw deck" + icon_state = "l6saw" + + mana_cost = 8 + attack = 6 + health = 0 + + faction = "Syndicate" + rarity = "Epic" + card_type = "Equipment" + +/datum/tcg_card/pack_nuclear/bulldog + name = "Bulldog Shotgun" + desc = "A semi-auto, mag-fed shotgun for combat in narrow corridors, nicknamed 'Bulldog' by boarding parties. Compatible only with specialized 8-round drum magazines." + rules = "After attack, deal 1 damage to enemy units next to the attacked one." + icon_state = "bulldog" + + mana_cost = 3 + attack = 3 + health = 0 + + faction = "Syndicate" + rarity = "Rare" + card_type = "Equipment" + +/datum/tcg_card/pack_nuclear/nuke_op_leader + name = "Nuclear Team Commander" + desc = "All commanders of elite nuclear teams are equipped with high-tier gear and weaponery. And, sometimes, gaming cards." + rules = "Squad Tactics. Give all Syndicate units on your side +1/0." + icon_state = "nuke_op_leader" + + mana_cost = 5 + attack = 3 + health = 4 + + faction = "Syndicate" + rarity = "Epic" + card_type = "Unit" + +/datum/tcg_card/pack_nuclear/nuke_op + name = "Nuclear Team Commander" + desc = "An unequipped nuclear operative, ready to buy some gear and go full ham!" + rules = "Squad Tactics. On summon: Search your deck for Syndicate equipment. Equip it on this unit. Shuffle it afterwards." + icon_state = "nuke_op" + + mana_cost = 3 + attack = 2 + health = 3 + + faction = "Syndicate" + rarity = "Common" + card_type = "Unit" + +/datum/tcg_card/pack_nuclear/dark_gygax + name = "Dark Gygax" + desc = "A lightweight exosuit, painted in a dark scheme. This model appears to have some modifications." + rules = "Squad Tactics." + icon_state = "dark_gygax" + + mana_cost = 6 + attack = 8 + health = 4 + + faction = "Syndicate" + rarity = "Epic" + card_type = "Unit" + +/datum/tcg_card/pack_nuclear/mauler + name = "Mauler" + desc = "Heavy-duty, combat exosuit, developed off of the existing Marauder model. A perfect killing machine equipped with best weaponery in the world." + rules = "Squad Tactics. Deadeye." + icon_state = "mauler" + + mana_cost = 8 + attack = 8 + health = 8 + + faction = "Syndicate" + rarity = "Legendary" + card_type = "Unit" + +/datum/tcg_card/pack_nuclear/saboteur + name = "Syndicate Saboteur Cyborg" + desc = "A streamlined engineering cyborg, equipped with covert modules. Allows to sabotage all the systems you want without being suspicious." + rules = "Block the first spell your opponent plays against your hero." + icon_state = "saboteur" + + mana_cost = 3 + attack = 1 + health = 3 + + faction = "Syndicate" + rarity = "Rare" + card_type = "Unit" + +/datum/tcg_card/pack_nuclear/medic + name = "Syndicate Medical Cyborg" + desc = "A combat medical cyborg. Has limited offensive potential, but makes more than up for it with its support capabilities." + rules = "Each turn you can give one of your units 0/+1." + icon_state = "medic" + + mana_cost = 4 + attack = 1 + health = 2 + + faction = "Syndicate" + rarity = "Rare" + card_type = "Unit" + +/datum/tcg_card/pack_nuclear/combat + name = "Syndicate Assault Cyborg" + desc = "A cyborg designed and programmed for systematic extermination of non-Syndicate personnel." + rules = "Squad Tactics. Fury." + icon_state = "combat" + + mana_cost = 5 + attack = 4 + health = 4 + + faction = "Syndicate" + rarity = "Epic" + card_type = "Unit" + +/datum/tcg_card/pack_nuclear/emag + name = "Cryptographic Sequencer" + desc = "It's a card with a magnetic strip attached to some circuitry." + rules = "Convert an enemy silicon unit to your side." + icon_state = "emag" + + mana_cost = 4 + + faction = "Syndicate" + rarity = "Common" + card_type = "Spell" + +/datum/tcg_card/pack_nuclear/bomb + name = "Syndicate Bomb" + desc = "A large and menacing device. Can be bolted down with a wrench." + rules = "Deal 6 damage to all units on the field after 2 turns." + icon_state = "bomb" + + mana_cost = 6 + + faction = "Syndicate" + rarity = "Rare" + card_type = "Spell" + +/datum/tcg_card/pack_nuclear/honkbomb + name = "H.O.N.K. Bomb" + desc = "A bomb filled to the brim with bananium and dehydrated clowns!" + rules = "Search your deck for up to 3 Clowns. Play them for free. Shuffle the deck afterwards." + icon_state = "honkbomb" + + mana_cost = 8 + + faction = "Syndicate" + rarity = "Epic" + card_type = "Spell" + +/datum/tcg_card/pack_nuclear/assault_pod + name = "Assault Pod" + desc = "Raining Steel. Nothing personnel, just disky." + rules = "Summon up to 3 units from your hand with 4 mana discount each." + icon_state = "assault_pod" + + mana_cost = 8 + + faction = "Syndicate" + rarity = "Epic" + card_type = "Spell" + +/datum/tcg_card/pack_nuclear/c4 + name = "C4" + desc = "A bunch of plastic explosives wired together." + rules = "Deal 2 damage to an enemy unit." + icon_state = "c4" + + mana_cost = 1 + + faction = "Syndicate" + rarity = "Common" + card_type = "Spell" + +/datum/tcg_card/pack_nuclear/emp + name = "EMP Grenade" + desc = "A modern-looking grenade which creates a powerful EMP upon activation. Do not eat." + rules = "Deal 2 damage to an enemy silicon unit." + icon_state = "emp" + + mana_cost = 0 + + faction = "Syndicate" + rarity = "Common" + card_type = "Spell" + +/datum/tcg_card/pack_nuclear/zombie + name = "Romerol Zombie" + desc = "A horrible abomination, resembling a dead human. Has green skin and red claws. Wait, is it blood dripping from them?" + rules = "After killing an enemy unit, search your deck for a Zombie and summon it for free." + icon_state = "zombie" + + mana_cost = 8 + attack = 4 + health = 3 + + faction = "Syndicate" + rarity = "Epic" + card_type = "Unit" + +/datum/tcg_card/pack_nuclear/north_star + name = "North Star Armbands" + desc = "The armbands of a deadly martial artist. Makes you pretty keen to put an end to evil in an extremely violent manner." + rules = "Equipped unit can attack twice per turn." + icon_state = "north_star" + + mana_cost = 4 + + faction = "Syndicate" + rarity = "Rare" + card_type = "Equipment" + +/datum/tcg_card/pack_nuclear/fastdetonation + name = "Big-Ass Red Button" + desc = "A menacing red button. What could it do?" + rules = "Activate all spells that require several turns to occur." + icon_state = "fastdetonation" + + mana_cost = 2 + + faction = "Syndicate" + rarity = "Common" + card_type = "Spell" + +/datum/tcg_card/pack_nuclear/rpg + name = "PML-9 Rocket Launcher" + desc = "A reusable rocket propelled grenade launcher. The words \"NT this way\" and an arrow have been written near the barrel." + rules = "When equipped unit attacks enemy units, flip a coin. If heads, destroy the unit. If tails, deal 1/2 damage instead of the full blow." + icon_state = "rpg" + + mana_cost = 8 + attack = 6 + + faction = "Syndicate" + rarity = "Legendary" + card_type = "Equipment" + +/datum/tcg_card/pack_nuclear/darkhonk + name = "Dark H.O.N.K. Mech" + desc = "Produced by \"Tyranny of Honk, INC\", this exosuit is designed as heavy clown-support. This one was painted black for maximum HONKing!" + rules = "Taunt. Squad Tactics. Blocker." + icon_state = "darkhonk" + + mana_cost = 8 + attack = 6 + health = 8 + + faction = "Syndicate" + rarity = "Epic" + card_type = "Unit" + +/datum/tcg_card/pack_nuclear/shielded_hardsuit + name = "Shielded Blood-red Hardsuit" + desc = "An advanced version of Gorlex Maradeurs' hardsuit with built-in energy shielding." + rules = "Give equipped unit First Strike." + icon_state = "shielded_hardsuit" + + mana_cost = 4 + attack = 0 + health = 4 + + faction = "Syndicate" + rarity = "Epic" + card_type = "Equipment" + +/datum/tcg_card/pack_nuclear/nuclear_disk + name = "Nuclear Authentication Disk" + desc = "Better keep this safe." + rules = "Give equipped unit Taunt. After the equipped unit dies, re-equip this card to the killer." + icon_state = "nuclear_disk" + + mana_cost = 0 + attack = 1 + health = 1 + + faction = "Syndicate" + rarity = "Epic" + card_type = "Equipment" + +/datum/tcg_card/pack_nuclear/buzzkill + name = "Buzzkill grenade" + desc = "A whole swarm of angry bees filled with deadly toxins. Nasty!" + rules = "Hivemind." + icon_state = "buzzkill" + + mana_cost = 4 + attack = 1 + health = 5 + + faction = "Syndicate" + rarity = "Rare" + card_type = "Unit" + +/datum/tcg_card/pack_nuclear/syndicate_minibomb + name = "Syndicate Minibomb" + desc = "A syndicate manufactured explosive used to sow destruction and chaos." + rules = "Deal 3 damage to an enemy unit and units adjacent to it." + icon_state = "syndicate_minibomb" + + mana_cost = 3 + + faction = "Syndicate" + rarity = "Common" + card_type = "Spell" + +/datum/tcg_card/pack_nuclear/viscerator + name = "Viscerator" + desc = "A small yet deadly machine, designed to rip it's targets apart." + rules = "Gain +1/+1 for every other viscerator on field." + icon_state = "viscerator" + + mana_cost = 2 + attack = 3 + health = 1 + + faction = "Syndicate" + rarity = "Common" + card_type = "Unit" + +/datum/tcg_card/pack_nuclear/cqc + name = "CQC Manual" + desc = "A manual that teaches a single user tactical Close-Quarters Combat before self-destructing." + rules = "Give equipped unit Deadeye and First Strike." + icon_state = "cqc" + + mana_cost = 4 + attack = 4 + health = 3 + + faction = "Syndicate" + rarity = "Epic" + card_type = "Equipment" + +/datum/tcg_card/pack_nuclear/holoparasite + name = "Holoparasite" + desc = "A mysterious being that stands by its charge, ever vigilant." + rules = "On summon: \"Link\" this unit to another unit. Whenever this unit takes damage, instead, transfer all damage to the linked unit." + icon_state = "holoparasite" + + mana_cost = 6 + attack = 8 + health = 0 + + faction = "Syndicate" + rarity = "Legendary" + card_type = "Unit" + +/datum/tcg_card/pack_nuclear/rapier + name = "Rapier" + desc = "An elegant plastitanium rapier with a diamond tip and coated in a specialized knockout poison." + rules = "" + icon_state = "rapier" + + mana_cost = 2 + attack = 3 + health = 0 + + faction = "Syndicate" + rarity = "Rare" + card_type = "Equipment" + +/datum/tcg_card/pack_nuclear/sniper + name = "Sniper Rifle" + desc = "A long ranged weapon that does significant damage. No, you can't quickscope." + rules = "Give equipped unit Deadeye." + icon_state = "sniper" + + mana_cost = 6 + attack = 5 + health = 0 + + faction = "Syndicate" + rarity = "Epic" + card_type = "Equipment" + +/datum/tcg_card/pack_nuclear/honksword + name = "Bananium Sword" + desc = "An elegant weapon, for a more \"civilized\" age." + rules = "Equipped unit does not deal damage. Instead, it taps the attacked card without activating it's effects." + icon_state = "honksword" + + mana_cost = 3 + attack = 0 + health = 0 + + faction = "Syndicate" + rarity = "Common" + card_type = "Equipment" + +/datum/tcg_card/pack_nuclear/mustache + name = "Mustache Grenade" + desc = "A handsomely-attired teargas grenade." + rules = "Unequip all enemy units. Unequipped equipment cards must be discarded." + icon_state = "mustache" + + mana_cost = 5 + + faction = "Syndicate" + rarity = "Common" + card_type = "Spell" + +/datum/tcg_card/pack_nuclear/taeclowndo + name = "Tae-Clown-Do" + desc = "A pair of clown shoes, infused with bananium. Rumors say that these can teach their wearer the art of Tae-Clown-Do." + rules = "Flip a coin. If heads, your enemy skips a turn. If tails, you skip a turn instead." + icon_state = "taeclowndo" + + mana_cost = 3 + + faction = "Syndicate" + rarity = "Epic" + card_type = "Spell" diff --git a/code/modules/tcg/pack_star.dm b/code/modules/tcg/pack_star.dm new file mode 100644 index 0000000000..4b8e32e631 --- /dev/null +++ b/code/modules/tcg/pack_star.dm @@ -0,0 +1,304 @@ +/datum/tcg_card/pack_star + pack = 'icons/obj/tcg/pack_star.dmi' + +/datum/tcg_card/pack_star/golem + name = "Adamantine Golem" + desc = "An adamantine golem, immune to magic and being able to coordinate other golems, has a great power in combat." + rules = "Holy. Taunt." + icon_state = "golem" + + mana_cost = 4 + attack = 4 + health = 5 + + faction = "Unique" + rarity = "Rare" + card_type = "Unit" + +/obj/item/tcg_card/special/golem + datum_type = /datum/tcg_card/pack_star/golem + +/datum/tcg_card/pack_star/xenomaid + name = "Lusty Xenomorph Maid" + desc = "Just a lusty xenomorph maid, nothing to see here." + rules = "Blocker. Each turn, gain -1/-1." + icon_state = "xenomaid" + + mana_cost = 3 + attack = 6 + health = 6 + + faction = "Unique" + rarity = "Epic" + card_type = "Unit" + +/obj/item/tcg_card/special/xenomaid + datum_type = /datum/tcg_card/pack_star/xenomaid + +/datum/tcg_card/pack_star/morph + name = "Morph" + desc = "A revolting, pulsating pile of flesh that can mimic everything it sees." + rules = "On summon: Copy stats of an opponent's card." + icon_state = "morph" + + mana_cost = 4 + attack = 0 + health = 1 + + faction = "Unique" + rarity = "Common" + card_type = "Unit" + +/obj/item/tcg_card/special/morph + datum_type = /datum/tcg_card/pack_star/morph + +/datum/tcg_card/pack_star/demonic_miner + name = "Demonic Miner" + desc = "An soul of extremely geared miner, driven crazy or possessed by the demonic forces here, either way a terrifying enemy." + rules = "Each turn: Deal 1 damage to all the creatures on the field." + icon_state = "demonic_miner" + + mana_cost = 7 + attack = 4 + health = 5 + + faction = "Unique" + rarity = "Rare" + card_type = "Unit" + +/obj/item/tcg_card/special/demonic_miner + datum_type = /datum/tcg_card/pack_star/demonic_miner + +/datum/tcg_card/pack_star/wendigo + name = "Wendigo" + desc = "A mythological man-eating legendary creature, you probably aren't going to survive this." + rules = "" + icon_state = "wendigo" + + mana_cost = 6 + attack = 5 + health = 3 + + faction = "Unique" + rarity = "Common" + card_type = "Unit" + +/obj/item/tcg_card/special/wendigo + datum_type = /datum/tcg_card/pack_star/wendigo + +/datum/tcg_card/pack_star/honk + name = "H.O.N.K. Mech" + desc = "Produced by \"Tyranny of Honk, INC\", this exosuit is designed as heavy clown-support. Used to spread the fun and joy of life. HONK!" + rules = "Taunt." + icon_state = "honk" + + mana_cost = 8 + attack = 6 + health = 8 + + faction = "Unique" + rarity = "Epic" + card_type = "Unit" + +/obj/item/tcg_card/special/honk + datum_type = /datum/tcg_card/pack_star/honk + +/datum/tcg_card/pack_star/ratvar + name = "Clockwork Slab" + desc = "A link between clockwork servants and the Celestial Derelict. It contains information, recites scripture, and is Servant's most vital tool." + rules = "Equipped unit gains Clockwork and can't attack units with Holy." + icon_state = "ratvar" + + mana_cost = 2 + attack = 3 + health = 0 + + faction = "Unique" + rarity = "Common" + card_type = "Equipment" + +/obj/item/tcg_card/special/ratvar + datum_type = /datum/tcg_card/pack_star/ratvar + +/datum/tcg_card/pack_star/hierophant + name = "Hierophant Club" + desc = "The strange technology of this large club allows various nigh-magical feats. It used to beat you, but now you can set the beat." + rules = "Give equipped unit First Strike." + icon_state = "hierophant" + + mana_cost = 4 + attack = 2 + health = 0 + + faction = "Unique" + rarity = "Rare" + card_type = "Equipment" + +/obj/item/tcg_card/special/hierophant + datum_type = /datum/tcg_card/pack_star/hierophant + +/datum/tcg_card/pack_star/abductor + name = "Alien Gland" + desc = "A nausea-inducing hunk of twisting flesh and metal. These things are often found after people were abducted by grey-skinned aliens." + rules = "Each turn: Flip a coin. If heads, unit gain +1/+1. If tails, unit gains -2/-1." + icon_state = "abductor" + + mana_cost = 2 + attack = 0 + health = 0 + + faction = "Unique" + rarity = "Common" + card_type = "Equipment" + +/obj/item/tcg_card/special/abductor + datum_type = /datum/tcg_card/pack_star/abductor + +/datum/tcg_card/pack_star/space_carp + name = "Space Carp" + desc = "A failed weaponery experiment, looking like a ferocious, fang-bearing creature that resembles a fish." + rules = "" + icon_state = "space_carp" + + mana_cost = 1 + attack = 2 + health = 1 + + faction = "Unique" + rarity = "Common" + card_type = "Unit" + +/obj/item/tcg_card/special/space_carp + datum_type = /datum/tcg_card/pack_star/space_carp + +/datum/tcg_card/pack_star/spess_pirate + name = "Space Pirate" + desc = "Space Pirate does whatever he wants because he is free. Sadly, Space Rum insn't free." + rules = "On summon: Draw 2 cards. If there are no spells, discard them." + icon_state = "spess_pirate" + + mana_cost = 4 + attack = 3 + health = 2 + + faction = "Unique" + rarity = "Rare" + card_type = "Unit" + +/obj/item/tcg_card/special/spess_pirate + datum_type = /datum/tcg_card/pack_star/spess_pirate + +/datum/tcg_card/pack_star/gondola + name = "Gondola" + desc = "Gondola is the silent walker. Having no hands he embodies the Taoist principle of wu-wei (non-action) while his smiling facial expression shows his utter and complete acceptance of the world as it is. Its hide is extremely valuable." + rules = "Taunt. Holy." + icon_state = "gondola" + + mana_cost = 6 + attack = 0 + health = 6 + + faction = "Unique" + rarity = "Epic" + card_type = "Unit" + +/obj/item/tcg_card/special/gondola + datum_type = /datum/tcg_card/pack_star/gondola + +/datum/tcg_card/pack_star/phazon + name = "Phazon" + desc = "The pinnacle of scientific research and pride of Nanotrasen, Phazon uses cutting edge bluespace technology and expensive materials." + rules = "Whenever this unit takes damage, flip a coin. If heads, take no damage. If tails, take double damage." + icon_state = "phazon" + + mana_cost = 8 + attack = 5 + health = 7 + + faction = "Unique" + rarity = "Rare" + card_type = "Unit" + +/obj/item/tcg_card/special/phazon + datum_type = /datum/tcg_card/pack_star/phazon + +//Ultimate Exodia cards. I really, really doubt that someone will ever find them. + +/datum/tcg_card/exodia + pack = 'icons/obj/tcg/pack_star.dmi' + +/datum/tcg_card/exodia/exodia_singulo + name = "Singularity" + desc = "A monstrous gravitational singularity, pitch black(but not quiet) and very menacings." + rules = "This card doesn't leave field. At the end of each turn: Remove all the cards(except other Exodia cards) from the field." + icon_state = "exodia_singularity" + + mana_cost = 8 + + faction = "Exodia" + rarity = "Exodia" + card_type = "Spell" + +/datum/tcg_card/exodia/exodia_tesla + name = "Energy Orb" + desc = "An orb made out of hypercharged plasma. An ultimate bug zapper." + rules = "This card doesn't leave field. Every turn all units take 4 damage." + icon_state = "exodia_tesla" + + mana_cost = 8 + + faction = "Exodia" + rarity = "Exodia" + card_type = "Spell" + +/datum/tcg_card/exodia/exodia_narie + name = "Nar-Sie" + desc = "An avatar of the Nar-Sie, one of the Eldritch Gods." + rules = "This card doesn't leave field. Every turn all units take 1 damage and you restore 1 lifeshard." + icon_state = "exodia_narsie" + + mana_cost = 8 + + faction = "Exodia" + rarity = "Exodia" + card_type = "Spell" + +/datum/tcg_card/exodia/exodia_ratvar + name = "Ratvar" + desc = "Ratvar, the god of cogs and clockwork mechanisms, was trapped by Nar-Sie a long ago." + rules = "This card doesn't leave field. Every turn enemy hero recieves 2 lifeshard damage." + icon_state = "exodia_ratvar" + + mana_cost = 8 + + faction = "Exodia" + rarity = "Exodia" + card_type = "Spell" + +/datum/tcg_card/exodia/exodia + name = "Eldritch Horror" + desc = "The Eldritch Horror is a long forgotten demon that was the beginning of everything. Afterwards, his creations revolted and left him abadoned in endless void." + rules = "This card doesn't leave field. If all other 4 Exodia cards are on the field(Singularity, Energy Orb, Nar-Sie and Ratvar), the game is won." + icon_state = "exodia_eldritch" + + mana_cost = 8 + + faction = "Exodia" + rarity = "Unique" //No drop lads + card_type = "Spell" + +/obj/item/tcg_card/special/exodia_singulo + datum_type = /datum/tcg_card/exodia/exodia_singulo + +/obj/item/tcg_card/special/exodia_tesla + datum_type = /datum/tcg_card/exodia/exodia_tesla + +/obj/item/tcg_card/special/exodia_narie + datum_type = /datum/tcg_card/exodia/exodia_narie + +/obj/item/tcg_card/special/exodia_ratvar + datum_type = /datum/tcg_card/exodia/exodia_ratvar + +/obj/item/tcg_card/special/exodia + datum_type = /datum/tcg_card/exodia/exodia + diff --git a/code/modules/tgchat/to_chat.dm b/code/modules/tgchat/to_chat.dm index a50bf4595e..3030ec7fe9 100644 --- a/code/modules/tgchat/to_chat.dm +++ b/code/modules/tgchat/to_chat.dm @@ -1,4 +1,4 @@ -/** +/*! * Copyright (c) 2020 Aleksej Komarov * SPDX-License-Identifier: MIT */ @@ -47,9 +47,11 @@ * Sends the message to the recipient (target). * * Recommended way to write to_chat calls: + * ``` * to_chat(client, * type = MESSAGE_TYPE_INFO, * html = "You have found [object]") + * ``` */ /proc/to_chat(target, html, type = null, diff --git a/code/modules/tgs/v5/api.dm b/code/modules/tgs/v5/api.dm index 7a2ff694e0..466a986237 100644 --- a/code/modules/tgs/v5/api.dm +++ b/code/modules/tgs/v5/api.dm @@ -98,19 +98,18 @@ return json_encode(response) /datum/tgs_api/v5/OnTopic(T) + if(!initialized) + return FALSE //continue world/Topic + var/list/params = params2list(T) var/json = params[DMAPI5_TOPIC_DATA] if(!json) - return FALSE // continue to /world/Topic + return FALSE var/list/topic_parameters = json_decode(json) if(!topic_parameters) return TopicResponse("Invalid topic parameters json!"); - if(!initialized) - TGS_WARNING_LOG("Missed topic due to not being initialized: [T]") - return TRUE // too early to handle, but it's still our responsibility - var/their_sCK = topic_parameters[DMAPI5_PARAMETER_ACCESS_IDENTIFIER] if(their_sCK != access_identifier) return TopicResponse("Failed to decode [DMAPI5_PARAMETER_ACCESS_IDENTIFIER] from: [json]!"); diff --git a/code/modules/tgui/external.dm b/code/modules/tgui/external.dm index 8f47c609b2..565c595473 100644 --- a/code/modules/tgui/external.dm +++ b/code/modules/tgui/external.dm @@ -1,4 +1,4 @@ -/** +/*! * External tgui definitions, such as src_object APIs. * * Copyright (c) 2020 Aleksej Komarov @@ -71,12 +71,13 @@ * required action string The action/button that has been invoked by the user. * required params list A list of parameters attached to the button. * - * return bool If the UI should be updated or not. + * return bool If the user's input has been handled and the UI should update. */ /datum/proc/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + // SHOULD_CALL_PARENT(TRUE) // If UI is not interactive or usr calling Topic is not the UI user, bail. if(!ui || ui.status != UI_INTERACTIVE) - return 1 + return TRUE /** * public @@ -157,7 +158,7 @@ // Name the verb, and hide it from the user panel. set name = "uiclose" set hidden = TRUE - var/mob/user = src && src.mob + var/mob/user = src?.mob if(!user) return // Close all tgui datums based on window_id. diff --git a/code/modules/tgui/states.dm b/code/modules/tgui/states.dm index fa88cc1338..d5c0e5a5f5 100644 --- a/code/modules/tgui/states.dm +++ b/code/modules/tgui/states.dm @@ -1,4 +1,4 @@ -/** +/*! * Base state and helpers for states. Just does some sanity checks, * implement a proper state for in-depth checks. * @@ -78,32 +78,19 @@ /mob/living/silicon/ai/shared_ui_interaction(src_object) // Disable UIs if the AI is unpowered. + if(apc_override == src_object) //allows AI to (eventually) use the interface for their own APC even when out of power + return UI_INTERACTIVE if(lacks_power()) return UI_DISABLED return ..() /mob/living/silicon/robot/shared_ui_interaction(src_object) - // Disable UIs if the Borg is unpowered or locked. - if(!cell || cell.charge <= 0 || locked_down) + // Disable UIs if the object isn't installed in the borg AND the borg is either locked, has a dead cell, or no cell. + var/atom/device = src_object + if((istype(device) && device.loc != src) && (!cell || cell.charge <= 0 || locked_down)) return UI_DISABLED return ..() -/** - * public - * - * Check the distance for a living mob. - * Really only used for checks outside the context of a mob. - * Otherwise, use shared_living_ui_distance(). - * - * required src_object The object which owns the UI. - * required user mob The mob who opened/is using the UI. - * - * return UI_state The state of the UI. - */ -/atom/proc/contents_ui_distance(src_object, mob/living/user) - // Just call this mob's check. - return user.shared_living_ui_distance(src_object) - /** * public * diff --git a/code/modules/tgui/states/admin.dm b/code/modules/tgui/states/admin.dm index 227a294078..4da5061dfc 100644 --- a/code/modules/tgui/states/admin.dm +++ b/code/modules/tgui/states/admin.dm @@ -1,10 +1,12 @@ +/*! + * Copyright (c) 2020 Aleksej Komarov + * SPDX-License-Identifier: MIT + */ + /** * tgui state: admin_state * * Checks that the user is an admin, end-of-story. - * - * Copyright (c) 2020 Aleksej Komarov - * SPDX-License-Identifier: MIT */ GLOBAL_DATUM_INIT(admin_state, /datum/ui_state/admin_state, new) diff --git a/code/modules/tgui/states/always.dm b/code/modules/tgui/states/always.dm index 210f0896a2..2406dbb2b9 100644 --- a/code/modules/tgui/states/always.dm +++ b/code/modules/tgui/states/always.dm @@ -1,10 +1,12 @@ +/*! + * Copyright (c) 2020 Aleksej Komarov + * SPDX-License-Identifier: MIT + */ + /** * tgui state: always_state * * Always grants the user UI_INTERACTIVE. Period. - * - * Copyright (c) 2020 Aleksej Komarov - * SPDX-License-Identifier: MIT */ GLOBAL_DATUM_INIT(always_state, /datum/ui_state/always_state, new) diff --git a/code/modules/tgui/states/conscious.dm b/code/modules/tgui/states/conscious.dm index 670ca7c07e..8e35a97da3 100644 --- a/code/modules/tgui/states/conscious.dm +++ b/code/modules/tgui/states/conscious.dm @@ -1,10 +1,12 @@ +/*! + * Copyright (c) 2020 Aleksej Komarov + * SPDX-License-Identifier: MIT + */ + /** * tgui state: conscious_state * * Only checks if the user is conscious. - * - * Copyright (c) 2020 Aleksej Komarov - * SPDX-License-Identifier: MIT */ GLOBAL_DATUM_INIT(conscious_state, /datum/ui_state/conscious_state, new) diff --git a/code/modules/tgui/states/contained.dm b/code/modules/tgui/states/contained.dm index 1eb8edba25..98187b746e 100644 --- a/code/modules/tgui/states/contained.dm +++ b/code/modules/tgui/states/contained.dm @@ -1,10 +1,12 @@ +/*! + * Copyright (c) 2020 Aleksej Komarov + * SPDX-License-Identifier: MIT + */ + /** * tgui state: contained_state * * Checks that the user is inside the src_object. - * - * Copyright (c) 2020 Aleksej Komarov - * SPDX-License-Identifier: MIT */ GLOBAL_DATUM_INIT(contained_state, /datum/ui_state/contained_state, new) diff --git a/code/modules/tgui/states/deep_inventory.dm b/code/modules/tgui/states/deep_inventory.dm index a2b9276a59..a7351a0d2d 100644 --- a/code/modules/tgui/states/deep_inventory.dm +++ b/code/modules/tgui/states/deep_inventory.dm @@ -1,11 +1,13 @@ +/*! + * Copyright (c) 2020 Aleksej Komarov + * SPDX-License-Identifier: MIT + */ + /** * tgui state: deep_inventory_state * * Checks that the src_object is in the user's deep * (backpack, box, toolbox, etc) inventory. - * - * Copyright (c) 2020 Aleksej Komarov - * SPDX-License-Identifier: MIT */ GLOBAL_DATUM_INIT(deep_inventory_state, /datum/ui_state/deep_inventory_state, new) diff --git a/code/modules/tgui/states/default.dm b/code/modules/tgui/states/default.dm index 367e57beff..56220105a5 100644 --- a/code/modules/tgui/states/default.dm +++ b/code/modules/tgui/states/default.dm @@ -1,11 +1,13 @@ +/*! + * Copyright (c) 2020 Aleksej Komarov + * SPDX-License-Identifier: MIT + */ + /** * tgui state: default_state * * Checks a number of things -- mostly physical distance for humans * and view for robots. - * - * Copyright (c) 2020 Aleksej Komarov - * SPDX-License-Identifier: MIT */ GLOBAL_DATUM_INIT(default_state, /datum/ui_state/default, new) @@ -18,15 +20,10 @@ GLOBAL_DATUM_INIT(default_state, /datum/ui_state/default, new) /mob/living/default_can_use_topic(src_object) . = shared_ui_interaction(src_object) - if(. > UI_CLOSE && loc) - . = min(., loc.contents_ui_distance(src_object, src)) // Check the distance... - if(. == UI_INTERACTIVE) // Non-human living mobs can only look, not touch. - return UI_UPDATE - -/mob/living/carbon/human/default_can_use_topic(src_object) - . = shared_ui_interaction(src_object) - if(. > UI_CLOSE) + if(. > UI_CLOSE && loc) //must not be in nullspace. . = min(., shared_living_ui_distance(src_object)) // Check the distance... + if(. == UI_INTERACTIVE && !IsAdvancedToolUser(src)) // unhandy living mobs can only look, not touch. + return UI_UPDATE /mob/living/silicon/robot/default_can_use_topic(src_object) . = shared_ui_interaction(src_object) @@ -49,14 +46,9 @@ GLOBAL_DATUM_INIT(default_state, /datum/ui_state/default, new) return UI_INTERACTIVE return UI_CLOSE -/mob/living/simple_animal/default_can_use_topic(src_object) - . = shared_ui_interaction(src_object) - if(. > UI_CLOSE) - . = min(., shared_living_ui_distance(src_object)) //simple animals can only use things they're near. - /mob/living/silicon/pai/default_can_use_topic(src_object) // pAIs can only use themselves and the owner's radio. if((src_object == src || src_object == radio) && !stat) return UI_INTERACTIVE else - return ..() + return min(..(), UI_UPDATE) diff --git a/code/modules/tgui/states/hands.dm b/code/modules/tgui/states/hands.dm index 1c885ed414..e8cb844bf7 100644 --- a/code/modules/tgui/states/hands.dm +++ b/code/modules/tgui/states/hands.dm @@ -1,10 +1,12 @@ +/*! + * Copyright (c) 2020 Aleksej Komarov + * SPDX-License-Identifier: MIT + */ + /** * tgui state: hands_state * * Checks that the src_object is in the user's hands. - * - * Copyright (c) 2020 Aleksej Komarov - * SPDX-License-Identifier: MIT */ GLOBAL_DATUM_INIT(hands_state, /datum/ui_state/hands_state, new) diff --git a/code/modules/tgui/states/human_adjacent.dm b/code/modules/tgui/states/human_adjacent.dm index 2ac7c8637b..b9208f96cd 100644 --- a/code/modules/tgui/states/human_adjacent.dm +++ b/code/modules/tgui/states/human_adjacent.dm @@ -1,11 +1,13 @@ +/*! + * Copyright (c) 2020 Aleksej Komarov + * SPDX-License-Identifier: MIT + */ + /** * tgui state: human_adjacent_state * * In addition to default checks, only allows interaction for a * human adjacent user. - * - * Copyright (c) 2020 Aleksej Komarov - * SPDX-License-Identifier: MIT */ GLOBAL_DATUM_INIT(human_adjacent_state, /datum/ui_state/human_adjacent_state, new) diff --git a/code/modules/tgui/states/inventory.dm b/code/modules/tgui/states/inventory.dm index dc5dd0d57e..4bc121b278 100644 --- a/code/modules/tgui/states/inventory.dm +++ b/code/modules/tgui/states/inventory.dm @@ -1,11 +1,13 @@ +/*! + * Copyright (c) 2020 Aleksej Komarov + * SPDX-License-Identifier: MIT + */ + /** * tgui state: inventory_state * * Checks that the src_object is in the user's top-level * (hand, ear, pocket, belt, etc) inventory. - * - * Copyright (c) 2020 Aleksej Komarov - * SPDX-License-Identifier: MIT */ GLOBAL_DATUM_INIT(inventory_state, /datum/ui_state/inventory_state, new) diff --git a/code/modules/tgui/states/language_menu.dm b/code/modules/tgui/states/language_menu.dm index 6389b05cd5..eaaa125786 100644 --- a/code/modules/tgui/states/language_menu.dm +++ b/code/modules/tgui/states/language_menu.dm @@ -1,10 +1,12 @@ -/** - * tgui state: language_menu_state - * +/*! * Copyright (c) 2020 Aleksej Komarov * SPDX-License-Identifier: MIT */ +/** + * tgui state: language_menu_state + */ + GLOBAL_DATUM_INIT(language_menu_state, /datum/ui_state/language_menu, new) /datum/ui_state/language_menu/can_use_topic(src_object, mob/user) diff --git a/code/modules/tgui/states/new_player.dm b/code/modules/tgui/states/new_player.dm new file mode 100644 index 0000000000..cf6f83ed3a --- /dev/null +++ b/code/modules/tgui/states/new_player.dm @@ -0,0 +1,13 @@ +/** + * tgui state: new_player_state + * + * Checks that the user is a new_player, or if user is an admin + */ + +GLOBAL_DATUM_INIT(new_player_state, /datum/ui_state/new_player_state, new) + +/datum/ui_state/new_player_state/can_use_topic(src_object, mob/user) + if(isnewplayer(user) || check_rights_for(user.client, R_ADMIN)) + return UI_INTERACTIVE + return UI_CLOSE + diff --git a/code/modules/tgui/states/not_incapacitated.dm b/code/modules/tgui/states/not_incapacitated.dm index 16dcb7881e..48787c81a7 100644 --- a/code/modules/tgui/states/not_incapacitated.dm +++ b/code/modules/tgui/states/not_incapacitated.dm @@ -1,10 +1,12 @@ +/*! + * Copyright (c) 2020 Aleksej Komarov + * SPDX-License-Identifier: MIT + */ + /** * tgui state: not_incapacitated_state * * Checks that the user isn't incapacitated - * - * Copyright (c) 2020 Aleksej Komarov - * SPDX-License-Identifier: MIT */ GLOBAL_DATUM_INIT(not_incapacitated_state, /datum/ui_state/not_incapacitated_state, new) @@ -25,7 +27,7 @@ GLOBAL_DATUM_INIT(not_incapacitated_turf_state, /datum/ui_state/not_incapacitate turf_check = no_turfs /datum/ui_state/not_incapacitated_state/can_use_topic(src_object, mob/user) - if(user.stat) + if(user.stat != CONSCIOUS) return UI_CLOSE if(user.incapacitated() || (turf_check && !isturf(user.loc))) return UI_DISABLED diff --git a/code/modules/tgui/states/notcontained.dm b/code/modules/tgui/states/notcontained.dm index 1d4e6aec19..018e0fa030 100644 --- a/code/modules/tgui/states/notcontained.dm +++ b/code/modules/tgui/states/notcontained.dm @@ -1,11 +1,13 @@ +/*! + * Copyright (c) 2020 Aleksej Komarov + * SPDX-License-Identifier: MIT + */ + /** * tgui state: notcontained_state * * Checks that the user is not inside src_object, and then makes the * default checks. - * - * Copyright (c) 2020 Aleksej Komarov - * SPDX-License-Identifier: MIT */ GLOBAL_DATUM_INIT(notcontained_state, /datum/ui_state/notcontained_state, new) diff --git a/code/modules/tgui/states/observer.dm b/code/modules/tgui/states/observer.dm index d105de1c0c..b749afa894 100644 --- a/code/modules/tgui/states/observer.dm +++ b/code/modules/tgui/states/observer.dm @@ -1,10 +1,12 @@ +/*! + * Copyright (c) 2020 Aleksej Komarov + * SPDX-License-Identifier: MIT + */ + /** * tgui state: observer_state * * Checks that the user is an observer/ghost. - * - * Copyright (c) 2020 Aleksej Komarov - * SPDX-License-Identifier: MIT */ GLOBAL_DATUM_INIT(observer_state, /datum/ui_state/observer_state, new) diff --git a/code/modules/tgui/states/physical.dm b/code/modules/tgui/states/physical.dm index 3073039d14..b559758f72 100644 --- a/code/modules/tgui/states/physical.dm +++ b/code/modules/tgui/states/physical.dm @@ -1,10 +1,12 @@ +/*! + * Copyright (c) 2020 Aleksej Komarov + * SPDX-License-Identifier: MIT + */ + /** * tgui state: physical_state * * Short-circuits the default state to only check physical distance. - * - * Copyright (c) 2020 Aleksej Komarov - * SPDX-License-Identifier: MIT */ GLOBAL_DATUM_INIT(physical_state, /datum/ui_state/physical, new) diff --git a/code/modules/tgui/states/self.dm b/code/modules/tgui/states/self.dm index 4b6e3b9fd9..f7cef3f600 100644 --- a/code/modules/tgui/states/self.dm +++ b/code/modules/tgui/states/self.dm @@ -1,10 +1,12 @@ +/*! + * Copyright (c) 2020 Aleksej Komarov + * SPDX-License-Identifier: MIT + */ + /** * tgui state: self_state * * Only checks that the user and src_object are the same. - * - * Copyright (c) 2020 Aleksej Komarov - * SPDX-License-Identifier: MIT */ GLOBAL_DATUM_INIT(self_state, /datum/ui_state/self_state, new) diff --git a/code/modules/tgui/states/zlevel.dm b/code/modules/tgui/states/zlevel.dm index 64ea2fa1c0..f1a2282b3c 100644 --- a/code/modules/tgui/states/zlevel.dm +++ b/code/modules/tgui/states/zlevel.dm @@ -1,10 +1,12 @@ +/*! + * Copyright (c) 2020 Aleksej Komarov + * SPDX-License-Identifier: MIT + */ + /** * tgui state: z_state * * Only checks that the Z-level of the user and src_object are the same. - * - * Copyright (c) 2020 Aleksej Komarov - * SPDX-License-Identifier: MIT */ GLOBAL_DATUM_INIT(z_state, /datum/ui_state/z_state, new) diff --git a/code/modules/tgui/tgui.dm b/code/modules/tgui/tgui.dm index 9393b9641f..b99783f67a 100644 --- a/code/modules/tgui/tgui.dm +++ b/code/modules/tgui/tgui.dm @@ -1,4 +1,4 @@ -/** +/*! * Copyright (c) 2020 Aleksej Komarov * SPDX-License-Identifier: MIT */ @@ -50,7 +50,7 @@ */ /datum/tgui/New(mob/user, datum/src_object, interface, title, ui_x, ui_y) log_tgui(user, - "new [interface] fancy [user.client.prefs.tgui_fancy]", + "new [interface] fancy [user?.client?.prefs.tgui_fancy]", src_object = src_object) src.user = user src.src_object = src_object @@ -67,18 +67,20 @@ * public * * Open this UI (and initialize it with data). + * + * return bool - TRUE if a new pooled window is opened, FALSE in all other situations including if a new pooled window didn't open because one already exists. */ /datum/tgui/proc/open() if(!user.client) - return null + return FALSE if(window) - return null + return FALSE process_status() if(status < UI_UPDATE) - return null + return FALSE window = SStgui.request_pooled_window(user) if(!window) - return null + return FALSE opened_at = world.time window.acquire_lock(src) if(!window.is_ready()) @@ -101,6 +103,8 @@ with_static_data = TRUE)) SStgui.on_open(src) + return TRUE + /** * public * @@ -156,7 +160,7 @@ */ /datum/tgui/proc/send_asset(datum/asset/asset) if(!window) - CRASH("send_asset() can only be called after open().") + CRASH("send_asset() was called either without calling open() first or when open() did not return TRUE.") return window.send_asset(asset) /** @@ -237,7 +241,7 @@ * Run an update cycle for this UI. Called internally by SStgui * every second or so. */ -/datum/tgui/process(force = FALSE) +/datum/tgui/process(delta_time, force = FALSE) if(closing) return var/datum/host = src_object.ui_host(user) diff --git a/code/modules/tgui/tgui_alert.dm b/code/modules/tgui/tgui_alert.dm new file mode 100644 index 0000000000..1a86cca705 --- /dev/null +++ b/code/modules/tgui/tgui_alert.dm @@ -0,0 +1,160 @@ +/** + * Creates a TGUI alert window and returns the user's response. + * + * This proc should be used to create alerts that the caller will wait for a response from. + * Arguments: + * * user - The user to show the alert to. + * * message - The content of the alert, shown in the body of the TGUI window. + * * title - The of the alert modal, shown on the top of the TGUI window. + * * buttons - The options that can be chosen by the user, each string is assigned a button on the UI. + * * timeout - The timeout of the alert, after which the modal will close and qdel itself. Set to zero for no timeout. + */ +/proc/tgui_alert(mob/user, message, title, list/buttons, timeout = 60 SECONDS) + if (!user) + user = usr + if (!istype(user)) + if (istype(user, /client)) + var/client/client = user + user = client.mob + else + return + var/datum/tgui_modal/alert = new(user, message, title, buttons, timeout) + alert.ui_interact(user) + alert.wait() + if (alert) + . = alert.choice + qdel(alert) + +/** + * Creates an asynchronous TGUI alert window with an associated callback. + * + * This proc should be used to create alerts that invoke a callback with the user's chosen option. + * Arguments: + * * user - The user to show the alert to. + * * message - The content of the alert, shown in the body of the TGUI window. + * * title - The of the alert modal, shown on the top of the TGUI window. + * * buttons - The options that can be chosen by the user, each string is assigned a button on the UI. + * * callback - The callback to be invoked when a choice is made. + * * timeout - The timeout of the alert, after which the modal will close and qdel itself. Set to zero for no timeout. + */ +/proc/tgui_alert_async(mob/user, message, title, list/buttons, datum/callback/callback, timeout = 60 SECONDS) + if (!user) + user = usr + if (!istype(user)) + if (istype(user, /client)) + var/client/client = user + user = client.mob + else + return + var/datum/tgui_modal/async/alert = new(user, message, title, buttons, callback, timeout) + alert.ui_interact(user) + +/** + * # tgui_modal + * + * Datum used for instantiating and using a TGUI-controlled modal that prompts the user with + * a message and has buttons for responses. + */ +/datum/tgui_modal + /// The title of the TGUI window + var/title + /// The textual body of the TGUI window + var/message + /// The list of buttons (responses) provided on the TGUI window + var/list/buttons + /// The button that the user has pressed, null if no selection has been made + var/choice + /// The time at which the tgui_modal was created, for displaying timeout progress. + var/start_time + /// The lifespan of the tgui_modal, after which the window will close and delete itself. + var/timeout + /// Boolean field describing if the tgui_modal was closed by the user. + var/closed + +/datum/tgui_modal/New(mob/user, message, title, list/buttons, timeout) + src.title = title + src.message = message + src.buttons = buttons.Copy() + if (timeout) + src.timeout = timeout + start_time = world.time + QDEL_IN(src, timeout) + +/datum/tgui_modal/Destroy(force, ...) + SStgui.close_uis(src) + QDEL_NULL(buttons) + . = ..() + +/** + * Waits for a user's response to the tgui_modal's prompt before returning. Returns early if + * the window was closed by the user. + */ +/datum/tgui_modal/proc/wait() + while (!choice && !closed) + stoplag(1) + +/datum/tgui_modal/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "AlertModal") + ui.open() + +/datum/tgui_modal/ui_close(mob/user) + . = ..() + closed = TRUE + +/datum/tgui_modal/ui_state(mob/user) + return GLOB.always_state + +/datum/tgui_modal/ui_data(mob/user) + . = list( + "title" = title, + "message" = message, + "buttons" = buttons + ) + + if(timeout) + .["timeout"] = CLAMP01((timeout - (world.time - start_time) - 1 SECONDS) / (timeout - 1 SECONDS)) + +/datum/tgui_modal/ui_act(action, list/params) + . = ..() + if (.) + return + switch(action) + if("choose") + if (!(params["choice"] in buttons)) + return + choice = params["choice"] + SStgui.close_uis(src) + return TRUE + +/** + * # async tgui_modal + * + * An asynchronous version of tgui_modal to be used with callbacks instead of waiting on user responses. + */ +/datum/tgui_modal/async + /// The callback to be invoked by the tgui_modal upon having a choice made. + var/datum/callback/callback + +/datum/tgui_modal/async/New(mob/user, message, title, list/buttons, callback, timeout) + ..(user, title, message, buttons, timeout) + src.callback = callback + +/datum/tgui_modal/async/Destroy(force, ...) + QDEL_NULL(callback) + . = ..() + +/datum/tgui_modal/async/ui_close(mob/user) + . = ..() + qdel(src) + +/datum/tgui_modal/async/ui_act(action, list/params) + . = ..() + if (!. || choice == null) + return + callback.InvokeAsync(choice) + qdel(src) + +/datum/tgui_modal/async/wait() + return diff --git a/code/modules/tgui/tgui_input_list.dm b/code/modules/tgui/tgui_input_list.dm new file mode 100644 index 0000000000..242b69a934 --- /dev/null +++ b/code/modules/tgui/tgui_input_list.dm @@ -0,0 +1,184 @@ +/** + * Creates a TGUI input list window and returns the user's response. + * + * This proc should be used to create alerts that the caller will wait for a response from. + * Arguments: + * * user - The user to show the input box to. + * * message - The content of the input box, shown in the body of the TGUI window. + * * title - The title of the input box, shown on the top of the TGUI window. + * * buttons - The options that can be chosen by the user, each string is assigned a button on the UI. + * * timeout - The timeout of the input box, after which the input box will close and qdel itself. Set to zero for no timeout. + */ +/proc/tgui_input_list(mob/user, message, title, list/buttons, timeout = 0) + if (!user) + user = usr + if(!length(buttons)) + return + if (!istype(user)) + if (istype(user, /client)) + var/client/client = user + user = client.mob + else + return + var/datum/tgui_list_input/input = new(user, message, title, buttons, timeout) + input.ui_interact(user) + input.wait() + if (input) + . = input.choice + qdel(input) + +/** + * Creates an asynchronous TGUI input list window with an associated callback. + * + * This proc should be used to create inputs that invoke a callback with the user's chosen option. + * Arguments: + * * user - The user to show the input box to. + * * message - The content of the input box, shown in the body of the TGUI window. + * * title - The title of the input box, shown on the top of the TGUI window. + * * buttons - The options that can be chosen by the user, each string is assigned a button on the UI. + * * callback - The callback to be invoked when a choice is made. + * * timeout - The timeout of the input box, after which the menu will close and qdel itself. Set to zero for no timeout. + */ +/proc/tgui_input_list_async(mob/user, message, title, list/buttons, datum/callback/callback, timeout = 60 SECONDS) + if (!user) + user = usr + if(!length(buttons)) + return + if (!istype(user)) + if (istype(user, /client)) + var/client/client = user + user = client.mob + else + return + var/datum/tgui_list_input/async/input = new(user, message, title, buttons, callback, timeout) + input.ui_interact(user) + +/** + * # tgui_list_input + * + * Datum used for instantiating and using a TGUI-controlled list input that prompts the user with + * a message and shows a list of selectable options + */ +/datum/tgui_list_input + /// The title of the TGUI window + var/title + /// The textual body of the TGUI window + var/message + /// The list of buttons (responses) provided on the TGUI window + var/list/buttons + /// Buttons (strings specifically) mapped to the actual value (e.g. a mob or a verb) + var/list/buttons_map + /// The button that the user has pressed, null if no selection has been made + var/choice + /// The time at which the tgui_list_input was created, for displaying timeout progress. + var/start_time + /// The lifespan of the tgui_list_input, after which the window will close and delete itself. + var/timeout + /// Boolean field describing if the tgui_list_input was closed by the user. + var/closed + +/datum/tgui_list_input/New(mob/user, message, title, list/buttons, timeout) + src.title = title + src.message = message + src.buttons = list() + src.buttons_map = list() + + // Gets rid of illegal characters + var/static/regex/whitelistedWords = regex(@{"([^\u0020-\u8000]+)"}) + + for(var/i in buttons) + var/string_key = whitelistedWords.Replace("[i]", "") + + src.buttons += string_key + src.buttons_map[string_key] = i + + + if (timeout) + src.timeout = timeout + start_time = world.time + QDEL_IN(src, timeout) + +/datum/tgui_list_input/Destroy(force, ...) + SStgui.close_uis(src) + QDEL_NULL(buttons) + . = ..() + +/** + * Waits for a user's response to the tgui_list_input's prompt before returning. Returns early if + * the window was closed by the user. + */ +/datum/tgui_list_input/proc/wait() + while (!choice && !closed) + stoplag(1) + +/datum/tgui_list_input/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "ListInput") + ui.open() + +/datum/tgui_list_input/ui_close(mob/user) + . = ..() + closed = TRUE + +/datum/tgui_list_input/ui_state(mob/user) + return GLOB.always_state + +/datum/tgui_list_input/ui_static_data(mob/user) + . = list( + "title" = title, + "message" = message, + "buttons" = buttons + ) + +/datum/tgui_list_input/ui_data(mob/user) + . = list() + if(timeout) + .["timeout"] = clamp((timeout - (world.time - start_time) - 1 SECONDS) / (timeout - 1 SECONDS), 0, 1) + +/datum/tgui_list_input/ui_act(action, list/params) + . = ..() + if (.) + return + switch(action) + if("choose") + if (!(params["choice"] in buttons)) + return + choice = buttons_map[params["choice"]] + SStgui.close_uis(src) + return TRUE + if("cancel") + SStgui.close_uis(src) + closed = TRUE + return TRUE + +/** + * # async tgui_list_input + * + * An asynchronous version of tgui_list_input to be used with callbacks instead of waiting on user responses. + */ +/datum/tgui_list_input/async + /// The callback to be invoked by the tgui_list_input upon having a choice made. + var/datum/callback/callback + +/datum/tgui_list_input/async/New(mob/user, message, title, list/buttons, callback, timeout) + ..(user, title, message, buttons, timeout) + src.callback = callback + +/datum/tgui_list_input/async/Destroy(force, ...) + QDEL_NULL(callback) + . = ..() + +/datum/tgui_list_input/async/ui_close(mob/user) + . = ..() + qdel(src) + +/datum/tgui_list_input/async/ui_act(action, list/params) + . = ..() + if (!. || choice == null) + return + callback.InvokeAsync(choice) + qdel(src) + +/datum/tgui_list_input/async/wait() + return diff --git a/code/modules/tgui/tgui_window.dm b/code/modules/tgui/tgui_window.dm index f24a46e33d..ae54b2dd3f 100644 --- a/code/modules/tgui/tgui_window.dm +++ b/code/modules/tgui/tgui_window.dm @@ -1,4 +1,4 @@ -/** +/*! * Copyright (c) 2020 Aleksej Komarov * SPDX-License-Identifier: MIT */ @@ -90,10 +90,11 @@ html = replacetextEx(html, "\n", inline_html) // Open the window client << browse(html, "window=[id];[options]") - // Instruct the client to signal UI when the window is closed. - winset(client, id, "on-close=\"uiclose [id]\"") // Detect whether the control is a browser is_browser = winexists(client, id) == "BROWSER" + // Instruct the client to signal UI when the window is closed. + if(!is_browser) + winset(client, id, "on-close=\"uiclose [id]\"") /** * public diff --git a/code/modules/tgui_panel/audio.dm b/code/modules/tgui_panel/audio.dm index e62c4b5bc1..6806961599 100644 --- a/code/modules/tgui_panel/audio.dm +++ b/code/modules/tgui_panel/audio.dm @@ -1,4 +1,4 @@ -/** +/*! * Copyright (c) 2020 Aleksej Komarov * SPDX-License-Identifier: MIT */ diff --git a/code/modules/tgui_panel/external.dm b/code/modules/tgui_panel/external.dm index 35aa31eca7..89973a925d 100644 --- a/code/modules/tgui_panel/external.dm +++ b/code/modules/tgui_panel/external.dm @@ -1,4 +1,4 @@ -/** +/*! * Copyright (c) 2020 Aleksej Komarov * SPDX-License-Identifier: MIT */ @@ -12,19 +12,21 @@ set name = "Fix chat" set category = "OOC" var/action - log_tgui(src, "Started fixing.", - context = "verb/fix_tgui_panel") - // Not ready - if(!tgui_panel?.is_ready()) - log_tgui(src, "Panel is not ready", - context = "verb/fix_tgui_panel") - tgui_panel.window.send_message("ping", force = TRUE) - action = alert(src, "Method: Pinging the panel.\nWait a bit and tell me if it's fixed", "", "Fixed", "Nope") - if(action == "Fixed") - log_tgui(src, "Fixed by sending a ping", - context = "verb/fix_tgui_panel") - return - // Catch all solution + log_tgui(src, "Started fixing.", context = "verb/fix_tgui_panel") + + nuke_chat() + + // Failed to fix + action = alert(src, "Did that work?", "", "Yes", "No, switch to old ui") + if (action == "No, switch to old ui") + winset(src, "output", "on-show=&is-disabled=0&is-visible=1") + winset(src, "browseroutput", "is-disabled=1;is-visible=0") + log_tgui(src, "Failed to fix.", context = "verb/fix_tgui_panel") + +/client/proc/nuke_chat() + // Catch all solution (kick the whole thing in the pants) + winset(src, "output", "on-show=&is-disabled=0&is-visible=1") + winset(src, "browseroutput", "is-disabled=1;is-visible=0") if(!tgui_panel || !istype(tgui_panel)) log_tgui(src, "tgui_panel datum is missing", context = "verb/fix_tgui_panel") @@ -33,15 +35,3 @@ // Force show the panel to see if there are any errors winset(src, "output", "is-disabled=1&is-visible=0") winset(src, "browseroutput", "is-disabled=0;is-visible=1") - action = alert(src, "Method: Reinitializing the panel.\nWait a bit and tell me if it's fixed", "", "Fixed", "Nope") - if(action == "Fixed") - log_tgui(src, "Fixed by calling 'initialize'", - context = "verb/fix_tgui_panel") - return - // Failed to fix - action = alert(src, "Welp, I'm all out of ideas. Try closing BYOND and reconnecting.\nWe could also disable tgui_panel and re-enable the old UI", "", "Thanks anyways", "Switch to old UI") - if (action == "Switch to old UI") - winset(src, "output", "on-show=&is-disabled=0&is-visible=1") - winset(src, "browseroutput", "is-disabled=1;is-visible=0") - log_tgui(src, "Failed to fix.", - context = "verb/fix_tgui_panel") diff --git a/code/modules/tgui_panel/telemetry.dm b/code/modules/tgui_panel/telemetry.dm index 79087d8500..e1abfb1e12 100644 --- a/code/modules/tgui_panel/telemetry.dm +++ b/code/modules/tgui_panel/telemetry.dm @@ -1,4 +1,4 @@ -/** +/*! * Copyright (c) 2020 Aleksej Komarov * SPDX-License-Identifier: MIT */ diff --git a/code/modules/tgui_panel/tgui_panel.dm b/code/modules/tgui_panel/tgui_panel.dm index b983484046..2f7c2e9105 100644 --- a/code/modules/tgui_panel/tgui_panel.dm +++ b/code/modules/tgui_panel/tgui_panel.dm @@ -1,4 +1,4 @@ -/** +/*! * Copyright (c) 2020 Aleksej Komarov * SPDX-License-Identifier: MIT */ @@ -37,6 +37,9 @@ * Initializes tgui panel. */ /datum/tgui_panel/proc/initialize(force = FALSE) + set waitfor = FALSE + // Minimal sleep to defer initialization to after client constructor + sleep(1) initialized_at = world.time // Perform a clean initialization window.initialize(inline_assets = list( @@ -46,7 +49,7 @@ window.send_asset(get_asset_datum(/datum/asset/simple/namespaced/fontawesome)) window.send_asset(get_asset_datum(/datum/asset/spritesheet/chat)) request_telemetry() - addtimer(CALLBACK(src, .proc/on_initialize_timed_out), 2 SECONDS) + addtimer(CALLBACK(src, .proc/on_initialize_timed_out), 5 SECONDS) /** * private @@ -55,7 +58,7 @@ */ /datum/tgui_panel/proc/on_initialize_timed_out() // Currently does nothing but sending a message to old chat. - SEND_TEXT(client, "Failed to load fancy chat, reverting to old chat. Certain features won't work.") + SEND_TEXT(client, "Failed to load fancy chat, click HERE to attempt to reload it.") /** * private diff --git a/code/modules/unit_tests/README.md b/code/modules/unit_tests/README.md new file mode 100644 index 0000000000..420c805fbf --- /dev/null +++ b/code/modules/unit_tests/README.md @@ -0,0 +1,70 @@ +# Unit Tests + +## What is unit testing? + +Unit tests are automated code to verify that parts of the game work exactly as they should. For example, [a test to make sure that the amputation surgery actually amputates the limb](https://github.com/tgstation/tgstation/blob/e416283f162b86345a8623125ab866839b1ac40d/code/modules/unit_tests/surgeries.dm#L1-L13). These are ran every time a PR is made, and thus are very helpful for preventing bugs from cropping up in your code that would've otherwise gone unnoticed. For example, would you have thought to check [that beach boys would still work the same after editing pizza](https://github.com/tgstation/tgstation/pull/53641#issuecomment-691384934)? If you value your time, probably not. + +On their most basic level, when `UNIT_TESTS` is defined, all subtypes of `/datum/unit_test` will have their `Run` proc executed. From here, if `Fail` is called at any point, then the tests will report as failed. + +## How do I write one? +1. Find a relevant file. + +All unit test related code is in `code/modules/unit_tests`. If you are adding a new test for a surgery, for example, then you'd open `surgeries.dm`. If a relevant file does not exist, simply create one in this folder, then `#include` it in `_unit_tests.dm`. + +2. Create the unit test. + +To make a new unit test, you simply need to define a `/datum/unit_test`. + +For example, let's suppose that we are creating a test to make sure a proc `square` correctly raises inputs to the power of two. We'd start with first: + +``` +/datum/unit_test/square/Run() +``` + +This defines our new unit test, `/datum/unit_test/square`. Inside this function, we're then going to run through whatever we want to check. Tests provide a few assertion functions to make this easy. For now, we're going to use `TEST_ASSERT_EQUAL`. + +``` +/datum/unit_test/square/Run() + TEST_ASSERT_EQUAL(square(3), 9, "square(3) did not return 9") + TEST_ASSERT_EQUAL(square(4), 16, "square(4) did not return 16") +``` + +As you can hopefully tell, we're simply checking if the output of `square` matches the output we are expecting. If the test fails, it'll report the error message given as well as whatever the actual output was. + +3. Run the unit test + +Open `code/_compile_options.dm` and uncomment the following line. + +``` +//#define UNIT_TESTS //If this is uncommented, we do a single run though of the game setup and tear down process with unit tests in between +``` + +Then, run tgstation.dmb in Dream Daemon. Don't bother trying to connect, you won't need to. You'll be able to see the outputs of all the tests. You'll get to see which tests failed and for what reason. If they all pass, you're set! + +## How to think about tests + +Unit tests exist to prevent bugs that would happen in a real game. Thus, they should attempt to emulate the game world wherever possible. For example, the [quick swap sanity test](https://github.com/tgstation/tgstation/blob/e416283f162b86345a8623125ab866839b1ac40d/code/modules/unit_tests/quick_swap_sanity.dm) emulates a *real* scenario of the bug it fixed occurring by creating a character and giving it real items. The unrecommended alternative would be to create special test-only items. This isn't a hard rule, the [reagent method exposure tests](https://github.com/tgstation/tgstation/blob/e416283f162b86345a8623125ab866839b1ac40d/code/modules/unit_tests/reagent_mod_expose.dm) create a test-only reagent for example, but do keep it in mind. + +Unit tests should also be just that--testing *units* of code. For example, instead of having one massive test for reagents, there are instead several smaller tests for testing exposure, metabolization, etc. + +## The unit testing API + +You can find more information about all of these from their respective doc comments, but for a brief overview: + +`/datum/unit_test` - The base for all tests to be ran. Subtypes must override `Run()`. `New()` and `Destroy()` can be used for setup and teardown. To fail, use `Fail(reason)`. + +`/datum/unit_test/proc/allocate(type, ...)` - Allocates an instance of the provided type with the given arguments. Is automatically destroyed when the test is over. Commonly seen in the form of `var/mob/living/carbon/human/human = allocate(/mob/living/carbon/human)`. + +`TEST_ASSERT(assertion, reason)` - Stops the unit test and fails if the assertion is not met. For example: `TEST_ASSERT(powered(), "Machine is not powered")`. + +`TEST_ASSERT_EQUAL(a, b, message)` - Same as `TEST_ASSERT`, but checks if `a == b`. If not, gives a helpful message showing what both `a` and `b` were. For example: `TEST_ASSERT_EQUAL(2 + 2, 4, "The universe is falling apart before our eyes!")`. + +`TEST_ASSERT_NOTEQUAL(a, b, message)` - Same as `TEST_ASSERT_EQUAL`, but reversed. + +`TEST_FOCUS(test_path)` - *Only* run the test provided within the parameters. Useful for reducing noise. For example, if we only want to run our example square test, we can add `TEST_FOCUS(/datum/unit_test/square)`. Should *never* be pushed in a pull request--you will be laughed at. + +## Final Notes + +- Writing tests before you attempt to fix the bug can actually speed up development a lot! It means you don't have to go in game and folllow the same exact steps manually every time. This process is known as "TDD" (test driven development). Write the test first, make sure it fails, *then* start work on the fix/feature, and you'll know you're done when your tests pass. If you do try this, do make sure to confirm in a non-testing environment just to double check. +- Make sure that your tests don't accidentally call RNG functions like `prob`. Since RNG is seeded during tests, you may not realize you have until someone else makes a PR and the tests fail! +- Do your best not to change the behavior of non-testing code during tests. While it may sometimes be necessary in the case of situations such as the above, it is still a slippery slope that can lead to the code you're testing being too different from the production environment to be useful. diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm index 37fa35946c..2745d971ff 100644 --- a/code/modules/unit_tests/_unit_tests.dm +++ b/code/modules/unit_tests/_unit_tests.dm @@ -1,42 +1,80 @@ //include unit test files in this module in this ifdef //Keep this sorted alphabetically -#ifdef UNIT_TESTS +#if defined(UNIT_TESTS) || defined(SPACEMAN_DMM) + /// Asserts that a condition is true /// If the condition is not true, fails the test #define TEST_ASSERT(assertion, reason) if (!(assertion)) { return Fail("Assertion failed: [reason || "No reason"]") } /// Asserts that the two parameters passed are equal, fails otherwise /// Optionally allows an additional message in the case of a failure -#define TEST_ASSERT_EQUAL(a, b, message) if ((a) != (b)) { return Fail("Expected [isnull(a) ? "null" : a] to be equal to [isnull(b) ? "null" : b].[message ? " [message]" : ""]") } +#define TEST_ASSERT_EQUAL(a, b, message) do { \ + var/lhs = ##a; \ + var/rhs = ##b; \ + if (lhs != rhs) { \ + return Fail("Expected [isnull(lhs) ? "null" : lhs] to be equal to [isnull(rhs) ? "null" : rhs].[message ? " [message]" : ""]"); \ + } \ +} while (FALSE) + +/// Asserts that the two parameters passed are not equal, fails otherwise +/// Optionally allows an additional message in the case of a failure +#define TEST_ASSERT_NOTEQUAL(a, b, message) do { \ + var/lhs = ##a; \ + var/rhs = ##b; \ + if (lhs == rhs) { \ + return Fail("Expected [isnull(lhs) ? "null" : lhs] to not be equal to [isnull(rhs) ? "null" : rhs].[message ? " [message]" : ""]"); \ + } \ +} while (FALSE) + +/// *Only* run the test provided within the parentheses +/// This is useful for debugging when you want to reduce noise, but should never be pushed +/// Intended to be used in the manner of `TEST_FOCUS(/datum/unit_test/math)` +#define TEST_FOCUS(test_path) ##test_path { focus = TRUE; } #include "anchored_mobs.dm" #include "bespoke_id.dm" -// #include "binary_insert.dm" -// #include "card_mismatch.dm" shame we don't have this! +#include "binary_insert.dm" +// #include "card_mismatch.dm" #include "chain_pull_through_space.dm" -#include "character_saving.dm" +// #include "combat.dm" #include "component_tests.dm" // #include "confusion.dm" -// #include "keybinding_init.dm" +// #include "emoting.dm" +// #include "heretic_knowledge.dm" +// #include "holidays.dm" +#include "initialize_sanity.dm" +#include "keybinding_init.dm" #include "machine_disassembly.dm" #include "medical_wounds.dm" +#include "merge_type.dm" // #include "metabolizing.dm" // #include "outfit_sanity.dm" +// #include "pills.dm" // #include "plantgrowth_tests.dm" -// #include "quick_swap_sanity.dm" - we don't have quick swap yet +// #include "projectiles.dm" #include "reagent_id_typos.dm" +// #include "reagent_mod_expose.dm" +// #include "reagent_mod_procs.dm" #include "reagent_recipe_collisions.dm" #include "resist.dm" -// #include "say.dm" //no saymods, someone update saycode please. +// #include "say.dm" +// #include "serving_tray.dm" // #include "siunit.dm" #include "spawn_humans.dm" // #include "species_whitelists.dm" +// #include "stomach.dm" #include "subsystem_init.dm" -// #include "surgeries.dm" // fails at random due to a race condition, commented out for now +#include "surgeries.dm" +#include "teleporters.dm" #include "timer_sanity.dm" #include "unit_test.dm" +/// CIT TESTS +#include "character_saving.dm" + #undef TEST_ASSERT #undef TEST_ASSERT_EQUAL +#undef TEST_ASSERT_NOTEQUAL +#undef TEST_FOCUS #endif diff --git a/code/modules/unit_tests/card_mismatch.dm b/code/modules/unit_tests/card_mismatch.dm new file mode 100644 index 0000000000..506e88f19c --- /dev/null +++ b/code/modules/unit_tests/card_mismatch.dm @@ -0,0 +1,7 @@ +/datum/unit_test/card_mismatch + +/datum/unit_test/card_mismatch/Run() + var/message = checkCardpacks(SStrading_card_game.card_packs) + message += checkCardDatums() + if(message) + Fail(message) diff --git a/code/modules/unit_tests/combat.dm b/code/modules/unit_tests/combat.dm new file mode 100644 index 0000000000..30bad72175 --- /dev/null +++ b/code/modules/unit_tests/combat.dm @@ -0,0 +1,98 @@ +/datum/unit_test/harm_punch/Run() + var/mob/living/carbon/human/puncher = allocate(/mob/living/carbon/human) + var/mob/living/carbon/human/victim = allocate(/mob/living/carbon/human) + + // Avoid all randomness in tests + ADD_TRAIT(puncher, TRAIT_PERFECT_ATTACKER, INNATE_TRAIT) + + puncher.a_intent_change(INTENT_HARM) + victim.attack_hand(puncher) + + TEST_ASSERT(victim.getBruteLoss() > 0, "Victim took no brute damage after being punched") + +/datum/unit_test/harm_melee/Run() + var/mob/living/carbon/human/tider = allocate(/mob/living/carbon/human) + var/mob/living/carbon/human/victim = allocate(/mob/living/carbon/human) + var/obj/item/storage/toolbox/toolbox = allocate(/obj/item/storage/toolbox) + + tider.put_in_active_hand(toolbox, forced = TRUE) + tider.a_intent_change(INTENT_HARM) + victim.attackby(toolbox, tider) + + TEST_ASSERT(victim.getBruteLoss() > 0, "Victim took no brute damage after being hit by a toolbox") + +/datum/unit_test/harm_different_damage/Run() + var/mob/living/carbon/human/attacker = allocate(/mob/living/carbon/human) + var/mob/living/carbon/human/victim = allocate(/mob/living/carbon/human) + var/obj/item/weldingtool/welding_tool = allocate(/obj/item/weldingtool) + + attacker.put_in_active_hand(welding_tool, forced = TRUE) + attacker.a_intent_change(INTENT_HARM) + welding_tool.attack_self(attacker) // Turn it on + victim.attackby(welding_tool, attacker) + + TEST_ASSERT_EQUAL(victim.getBruteLoss(), 0, "Victim took brute damage from a lit welding tool") + TEST_ASSERT(victim.getFireLoss() > 0, "Victim took no burn damage after being hit by a lit welding tool") + +/datum/unit_test/attack_chain + var/attack_hit + var/post_attack_hit + var/pre_attack_hit + +/datum/unit_test/attack_chain/proc/attack_hit() + attack_hit = TRUE + +/datum/unit_test/attack_chain/proc/post_attack_hit() + post_attack_hit = TRUE + +/datum/unit_test/attack_chain/proc/pre_attack_hit() + pre_attack_hit = TRUE + +/datum/unit_test/attack_chain/Run() + var/mob/living/carbon/human/attacker = allocate(/mob/living/carbon/human) + var/mob/living/carbon/human/victim = allocate(/mob/living/carbon/human) + var/obj/item/storage/toolbox/toolbox = allocate(/obj/item/storage/toolbox) + + RegisterSignal(toolbox, COMSIG_ITEM_PRE_ATTACK, .proc/pre_attack_hit) + RegisterSignal(toolbox, COMSIG_ITEM_ATTACK, .proc/attack_hit) + RegisterSignal(toolbox, COMSIG_ITEM_AFTERATTACK, .proc/post_attack_hit) + + attacker.put_in_active_hand(toolbox, forced = TRUE) + attacker.a_intent_change(INTENT_HARM) + toolbox.melee_attack_chain(attacker, victim) + + TEST_ASSERT(pre_attack_hit, "Pre-attack signal was not fired") + TEST_ASSERT(attack_hit, "Attack signal was not fired") + TEST_ASSERT(post_attack_hit, "Post-attack signal was not fired") + +/datum/unit_test/disarm/Run() + var/mob/living/carbon/human/attacker = allocate(/mob/living/carbon/human) + var/mob/living/carbon/human/victim = allocate(/mob/living/carbon/human) + var/obj/item/storage/toolbox/toolbox = allocate(/obj/item/storage/toolbox) + + victim.put_in_active_hand(toolbox, forced = TRUE) + attacker.a_intent_change(INTENT_DISARM) + + var/obj/structure/barricade/dense_object = allocate(/obj/structure/barricade) + + // Attacker --> Victim --> Empty space --> Wall + attacker.forceMove(run_loc_bottom_left) + victim.forceMove(locate(run_loc_bottom_left.x + 1, run_loc_bottom_left.y, run_loc_bottom_left.z)) + dense_object.forceMove(locate(run_loc_bottom_left.x + 3, run_loc_bottom_left.y, run_loc_bottom_left.z)) + + // First disarm, world should now look like: + // Attacker --> Empty space --> Victim --> Wall + victim.attack_hand(attacker) + + TEST_ASSERT_EQUAL(victim.loc.x, run_loc_bottom_left.x + 2, "Victim wasn't moved back after being pushed") + TEST_ASSERT(!victim.has_status_effect(STATUS_EFFECT_KNOCKDOWN), "Victim was knocked down despite not being against a wall") + TEST_ASSERT_EQUAL(victim.get_active_held_item(), toolbox, "Victim dropped toolbox despite not being against a wall") + + attacker.forceMove(get_step(attacker, EAST)) + + // Second disarm, victim was against wall and should be down + victim.attack_hand(attacker) + + TEST_ASSERT_EQUAL(victim.loc.x, run_loc_bottom_left.x + 2, "Victim was moved after being pushed against a wall") + TEST_ASSERT(victim.has_status_effect(STATUS_EFFECT_KNOCKDOWN), "Victim was not knocked down after being pushed against a wall") + TEST_ASSERT_EQUAL(victim.get_active_held_item(), null, "Victim didn't drop toolbox after being pushed against a wall") diff --git a/code/modules/unit_tests/confusion.dm b/code/modules/unit_tests/confusion.dm new file mode 100644 index 0000000000..8282493c96 --- /dev/null +++ b/code/modules/unit_tests/confusion.dm @@ -0,0 +1,16 @@ +// Checks that the confusion symptom correctly gives, and removes, confusion +/datum/unit_test/confusion_symptom/Run() + var/mob/living/carbon/human/H = allocate(/mob/living/carbon/human) + var/datum/disease/advance/confusion/disease = allocate(/datum/disease/advance/confusion) + var/datum/symptom/confusion/confusion = disease.symptoms[1] + disease.processing = TRUE + disease.update_stage(5) + disease.infect(H, make_copy = FALSE) + confusion.Activate(disease) + TEST_ASSERT(H.get_confusion() > 0, "Human is not confused after getting symptom.") + disease.cure() + TEST_ASSERT_EQUAL(H.get_confusion(), 0, "Human is still confused after curing confusion.") + +/datum/disease/advance/confusion/New() + symptoms += new /datum/symptom/confusion + Refresh() diff --git a/code/modules/unit_tests/emoting.dm b/code/modules/unit_tests/emoting.dm new file mode 100644 index 0000000000..5795ab3437 --- /dev/null +++ b/code/modules/unit_tests/emoting.dm @@ -0,0 +1,25 @@ +/datum/unit_test/emoting + var/emotes_used = 0 + +/datum/unit_test/emoting/Run() + var/mob/living/carbon/human/human = allocate(/mob/living/carbon/human) + RegisterSignal(human, COMSIG_MOB_EMOTE, .proc/on_emote_used) + + human.say("*shrug") + TEST_ASSERT_EQUAL(emotes_used, 1, "Human did not shrug") + + human.say("*beep") + TEST_ASSERT_EQUAL(emotes_used, 1, "Human beeped, when that should be restricted to silicons") + + human.setOxyLoss(140) + + TEST_ASSERT(human.stat != CONSCIOUS, "Human is somehow conscious after receiving suffocation damage") + + human.say("*shrug") + TEST_ASSERT_EQUAL(emotes_used, 1, "Human shrugged while unconscious") + + human.say("*deathgasp") + TEST_ASSERT_EQUAL(emotes_used, 2, "Human could not deathgasp while unconscious") + +/datum/unit_test/emoting/proc/on_emote_used() + emotes_used += 1 diff --git a/code/modules/unit_tests/heretic_knowledge.dm b/code/modules/unit_tests/heretic_knowledge.dm new file mode 100644 index 0000000000..a433bce1ec --- /dev/null +++ b/code/modules/unit_tests/heretic_knowledge.dm @@ -0,0 +1,21 @@ +/// This test checks all heretic knowledge nodes - excluding the ones which are unreachable on purpose - and ensures players can reach them in game. +/// If it finds a node that is unreachable, it throws an error. +/datum/unit_test/heretic_knowledge/Run() + ///List of all knowledge excluding the unreachable base types. + var/list/blacklist = list(/datum/eldritch_knowledge/spell,/datum/eldritch_knowledge/curse,/datum/eldritch_knowledge/final,/datum/eldritch_knowledge/summon) + var/list/all_possible_knowledge = subtypesof(/datum/eldritch_knowledge) - blacklist + + var/list/list_to_check = GLOB.heretic_start_knowledge.Copy() + var/i = 0 + while(i < length(list_to_check)) + var/datum/eldritch_knowledge/eldritch_knowledge = allocate(list_to_check[++i]) + for(var/next_knowledge in eldritch_knowledge.next_knowledge) + if(next_knowledge in list_to_check) + continue + list_to_check += next_knowledge + + if(length(all_possible_knowledge) != length(all_possible_knowledge & list_to_check)) + var/list/unreachables = all_possible_knowledge - list_to_check + for(var/X in unreachables) + var/datum/eldritch_knowledge/eldritch_knowledge = X + Fail("[initial(eldritch_knowledge.name)] is unreachable by players! Add it to the blacklist in /code/modules/unit_tests/heretic_knowledge.dm if it is purposeful!") diff --git a/code/modules/unit_tests/holidays.dm b/code/modules/unit_tests/holidays.dm new file mode 100644 index 0000000000..4df5443e2e --- /dev/null +++ b/code/modules/unit_tests/holidays.dm @@ -0,0 +1,33 @@ +// test Jewish holiday +/datum/unit_test/hanukkah_2123/Run() + var/datum/holiday/hebrew/hanukkah/hanukkah = new + TEST_ASSERT(hanukkah.shouldCelebrate(14, DECEMBER, 2123, 2, TUESDAY), "December 14, 2123 was not Hanukkah.") + +// test Islamic holiday +/datum/unit_test/ramadan_2165/Run() + var/datum/holiday/islamic/ramadan/ramadan = new + TEST_ASSERT(ramadan.shouldCelebrate(6, NOVEMBER, 2165, 1, WEDNESDAY), "November 6, 2165 was not Ramadan.") + +// nth day of week +/datum/unit_test/thanksgiving_2020/Run() + var/datum/holiday/nth_week/thanksgiving/thanksgiving = new + TEST_ASSERT(thanksgiving.shouldCelebrate(26, NOVEMBER, 2020, 4, THURSDAY), "November 26, 2020 was not Thanksgiving.") + +// another nth day of week +/datum/unit_test/indigenous_3683/Run() + var/datum/holiday/nth_week/indigenous/indigenous = new + TEST_ASSERT(indigenous.shouldCelebrate(11, OCTOBER, 3683, 2, MONDAY), "October 11, 3683 was not Indigenous Peoples' Day.") + +// plain old simple holiday +/datum/unit_test/hello_2020/Run() + var/datum/holiday/hello/hello = new + TEST_ASSERT(hello.shouldCelebrate(21, NOVEMBER, 2020, 3, SATURDAY), "November 21, 2020 was not Hello day.") + +// holiday which goes across months +/datum/unit_test/new_year_1983/Run() + var/datum/holiday/new_year/new_year = new + TEST_ASSERT(new_year.shouldCelebrate(2, JANUARY, 1983, 1, SUNDAY), "January 2, 1983 was not New Year.") + +/datum/unit_test/moth_week_2020/Run() + var/datum/holiday/moth/moth = new + TEST_ASSERT(moth.shouldCelebrate(19, JULY, 2020, 3, SATURDAY), "July 19, 2020 was not Moth Week.") diff --git a/code/modules/unit_tests/initialize_sanity.dm b/code/modules/unit_tests/initialize_sanity.dm new file mode 100644 index 0000000000..d183f530c8 --- /dev/null +++ b/code/modules/unit_tests/initialize_sanity.dm @@ -0,0 +1,11 @@ +/datum/unit_test/initialize_sanity/Run() + if(length(SSatoms.BadInitializeCalls)) + Fail("Bad Initialize() calls detected. Please read logs.") + var/list/init_failures_to_text = list( + "[BAD_INIT_QDEL_BEFORE]" = "Qdeleted Before Initialized", + "[BAD_INIT_DIDNT_INIT]" = "Did Not Initialize", + "[BAD_INIT_SLEPT]" = "Initialize() Slept", + "[BAD_INIT_NO_HINT]" = "No Initialize() Hint Returned", + ) + for(var/failure in SSatoms.BadInitializeCalls) + log_world("[failure]: [init_failures_to_text["[SSatoms.BadInitializeCalls[failure]]"]]") // You like stacked brackets? diff --git a/code/modules/unit_tests/keybinding_init.dm b/code/modules/unit_tests/keybinding_init.dm new file mode 100644 index 0000000000..2bd2fdee1e --- /dev/null +++ b/code/modules/unit_tests/keybinding_init.dm @@ -0,0 +1,6 @@ +/datum/unit_test/keybinding_init/Run() + for(var/i in subtypesof(/datum/keybinding)) + var/datum/keybinding/KB = i + if(initial(KB.keybind_signal) || !initial(KB.name)) + continue + Fail("[KB.name] does not have a keybind signal defined.") diff --git a/code/modules/unit_tests/machine_disassembly.dm b/code/modules/unit_tests/machine_disassembly.dm index bcc769bcf2..59edb4ae9d 100644 --- a/code/modules/unit_tests/machine_disassembly.dm +++ b/code/modules/unit_tests/machine_disassembly.dm @@ -3,11 +3,10 @@ var/obj/machinery/freezer = allocate(/obj/machinery/atmospherics/components/unary/thermomachine/freezer) var/turf/freezer_location = freezer.loc - freezer_location.ChangeTurf(/turf/open/floor/plasteel) freezer.deconstruct() // Check that the components are created TEST_ASSERT(locate(/obj/item/stock_parts/micro_laser) in freezer_location, "Couldn't find micro-laser when disassembling freezer") // Check that the circuit board itself is created - TEST_ASSERT(locate(/obj/item/circuitboard/machine/thermomachine/freezer) in freezer_location, "Couldn't find the circuit board when disassembling freezer") + TEST_ASSERT(locate(/obj/item/circuitboard/machine/thermomachine) in freezer_location, "Couldn't find the circuit board when disassembling freezer") diff --git a/code/modules/unit_tests/merge_type.dm b/code/modules/unit_tests/merge_type.dm new file mode 100644 index 0000000000..ba3cfcf492 --- /dev/null +++ b/code/modules/unit_tests/merge_type.dm @@ -0,0 +1,15 @@ +/datum/unit_test/merge_type/Run() + var/list/blacklist = list(/obj/item/stack/sheet, + /obj/item/stack/sheet/mineral, + /obj/item/stack/ore, + /obj/item/stack/spacecash, + // /obj/item/stack/license_plates, + /obj/item/stack/tile/mineral, + /obj/item/stack/tile) + + var/list/paths = subtypesof(/obj/item/stack) - blacklist + + for(var/stackpath in paths) + var/obj/item/stack/stack = stackpath + if(!initial(stack.merge_type)) + Fail("([stack]) lacks set merge_type variable!") diff --git a/code/modules/unit_tests/metabolizing.dm b/code/modules/unit_tests/metabolizing.dm index 895762c0ec..b7f8fc4f6a 100644 --- a/code/modules/unit_tests/metabolizing.dm +++ b/code/modules/unit_tests/metabolizing.dm @@ -17,3 +17,22 @@ /datum/unit_test/metabolization/Destroy() SSmobs.ignite() return ..() + +/datum/unit_test/on_mob_end_metabolize/Run() + var/mob/living/carbon/human/user = allocate(/mob/living/carbon/human) + var/obj/item/reagent_containers/pill/pill = allocate(/obj/item/reagent_containers/pill) + var/datum/reagent/drug/methamphetamine/meth = /datum/reagent/drug/methamphetamine + + // Give them enough meth to be consumed in 2 metabolizations + pill.reagents.add_reagent(meth, initial(meth.metabolization_rate) * 1.9) + pill.attack(user, user) + + user.Life() + + TEST_ASSERT(user.reagents.has_reagent(meth), "User does not have meth in their system after consuming it") + TEST_ASSERT(user.has_movespeed_modifier(/datum/movespeed_modifier/reagent/methamphetamine), "User consumed meth, but did not gain movespeed modifier") + + user.Life() + + TEST_ASSERT(!user.reagents.has_reagent(meth), "User still has meth in their system when it should've finished metabolizing") + TEST_ASSERT(!user.has_movespeed_modifier(/datum/movespeed_modifier/reagent/methamphetamine), "User still has movespeed modifier despite not containing any more meth") diff --git a/code/modules/unit_tests/outfit_sanity.dm b/code/modules/unit_tests/outfit_sanity.dm index 235820f9e9..57ce22434e 100644 --- a/code/modules/unit_tests/outfit_sanity.dm +++ b/code/modules/unit_tests/outfit_sanity.dm @@ -30,8 +30,8 @@ CHECK_OUTFIT_SLOT(glasses, ITEM_SLOT_EYES) CHECK_OUTFIT_SLOT(id, ITEM_SLOT_ID) CHECK_OUTFIT_SLOT(suit_store, ITEM_SLOT_SUITSTORE) - CHECK_OUTFIT_SLOT(l_pocket, ITEM_SLOT_POCKET) - CHECK_OUTFIT_SLOT(r_pocket, ITEM_SLOT_POCKET) + CHECK_OUTFIT_SLOT(l_pocket, ITEM_SLOT_LPOCKET) + CHECK_OUTFIT_SLOT(r_pocket, ITEM_SLOT_RPOCKET) if (outfit.backpack_contents || outfit.box) var/list/backpack_contents = outfit.backpack_contents?.Copy() diff --git a/code/modules/unit_tests/pills.dm b/code/modules/unit_tests/pills.dm new file mode 100644 index 0000000000..ed8f64ce95 --- /dev/null +++ b/code/modules/unit_tests/pills.dm @@ -0,0 +1,10 @@ +/datum/unit_test/pills/Run() + var/mob/living/carbon/human/human = allocate(/mob/living/carbon/human) + var/obj/item/reagent_containers/pill/iron/pill = allocate(/obj/item/reagent_containers/pill/iron) + + TEST_ASSERT_EQUAL(human.has_reagent(/datum/reagent/iron), FALSE, "Human somehow has iron before taking pill") + + pill.attack(human, human) + human.Life() + + TEST_ASSERT(human.has_reagent(/datum/reagent/iron), "Human doesn't have iron after taking pill") diff --git a/code/modules/unit_tests/projectiles.dm b/code/modules/unit_tests/projectiles.dm new file mode 100644 index 0000000000..53ceef01d6 --- /dev/null +++ b/code/modules/unit_tests/projectiles.dm @@ -0,0 +1,5 @@ +/datum/unit_test/projectile_movetypes/Run() + for(var/path in typesof(/obj/item/projectile)) + var/obj/projectile/projectile = path + if(initial(projectile.movement_type) & PHASING) + Fail("[path] has default movement type PHASING. Piercing projectiles should be done using the projectile piercing system, not movement_types!") diff --git a/code/modules/unit_tests/reagent_mod_expose.dm b/code/modules/unit_tests/reagent_mod_expose.dm new file mode 100644 index 0000000000..3fe02e044d --- /dev/null +++ b/code/modules/unit_tests/reagent_mod_expose.dm @@ -0,0 +1,59 @@ +// testing the mob expose procs are working + +/datum/reagent/method_patch_test + name = "method patch test" + +/datum/reagent/method_patch_test/expose_mob(mob/living/target, methods = PATCH, reac_volume, show_message = TRUE) + . = ..() + if(methods & PATCH) + target.health = 90 + if(methods & INJECT) + target.health = 80 + +/datum/unit_test/reagent_mob_expose/Run() + // Life() is handled just by tests + SSmobs.pause() + + var/mob/living/carbon/human/human = allocate(/mob/living/carbon/human) + var/obj/item/reagent_containers/dropper/dropper = allocate(/obj/item/reagent_containers/dropper) + var/obj/item/reagent_containers/food/drinks/drink = allocate(/obj/item/reagent_containers/food/drinks/bottle) + var/obj/item/reagent_containers/pill/patch/patch = allocate(/obj/item/reagent_containers/pill/patch) + var/obj/item/reagent_containers/syringe/syringe = allocate(/obj/item/reagent_containers/syringe) + + // INGEST + TEST_ASSERT_EQUAL(human.fire_stacks, 0, "Human has fire stacks before taking phlogiston") + drink.reagents.add_reagent(/datum/reagent/phlogiston, 10) + drink.attack(human, human) + TEST_ASSERT_EQUAL(human.fire_stacks, 1, "Human does not have fire stacks after taking phlogiston") + human.Life() + TEST_ASSERT(human.fire_stacks > 1, "Human fire stacks did not increase after life tick") + + // TOUCH + dropper.reagents.add_reagent(/datum/reagent/water, 1) + dropper.afterattack(human, human, TRUE) + TEST_ASSERT_EQUAL(human.fire_stacks, 0, "Human still has fire stacks after touching water") + + // VAPOR + TEST_ASSERT_EQUAL(human.drowsyness, 0, "Human is drowsy at the start of testing") + drink.reagents.clear_reagents() + drink.reagents.add_reagent(/datum/reagent/nitrous_oxide, 10) + drink.reagents.trans_to(human, 10, methods = VAPOR) + TEST_ASSERT_NOTEQUAL(human.drowsyness, 0, "Human is not drowsy after exposure to vapors") + + // PATCH + human.health = 100 + TEST_ASSERT_EQUAL(human.health, 100, "Human health did not set properly") + patch.reagents.add_reagent(/datum/reagent/method_patch_test, 1) + patch.self_delay = 0 + patch.attack(human, human) + TEST_ASSERT_EQUAL(human.health, 90, "Human health did not update after patch was applied") + + // INJECT + syringe.reagents.add_reagent(/datum/reagent/method_patch_test, 1) + syringe.mode = SYRINGE_INJECT + syringe.afterattack(human, human, TRUE) + TEST_ASSERT_EQUAL(human.health, 80, "Human health did not update after injection from syringe") + +/datum/unit_test/reagent_mob_expose/Destroy() + SSmobs.ignite() + return ..() diff --git a/code/modules/unit_tests/reagent_mod_procs.dm b/code/modules/unit_tests/reagent_mod_procs.dm new file mode 100644 index 0000000000..a2087f8624 --- /dev/null +++ b/code/modules/unit_tests/reagent_mod_procs.dm @@ -0,0 +1,12 @@ +/datum/unit_test/reagent_mob_procs/Run() + var/mob/living/carbon/human/human = allocate(/mob/living/carbon/human) + var/obj/item/food/hotdog/debug/fooditem = allocate(/obj/item/food/hotdog/debug) + + TEST_ASSERT_EQUAL(human.has_reagent(/datum/reagent/consumable/ketchup), FALSE, "Human somehow has ketchup before eating") + TEST_ASSERT_EQUAL(human.has_reagent(/datum/reagent/medicine/epinephrine), FALSE, "Human somehow has epinephrine before injecting") + + fooditem.attack(human, human) + human.reagents.add_reagent(/datum/reagent/medicine/epinephrine, 5) + + TEST_ASSERT(human.has_reagent(/datum/reagent/consumable/ketchup), "Human doesn't have ketchup after eating") + TEST_ASSERT(human.has_reagent(/datum/reagent/medicine/epinephrine), "Human doesn't have epinephrine after injecting") diff --git a/code/modules/unit_tests/say.dm b/code/modules/unit_tests/say.dm index 3fe6675ab4..a7df5ad624 100644 --- a/code/modules/unit_tests/say.dm +++ b/code/modules/unit_tests/say.dm @@ -10,7 +10,6 @@ test(";%Never gonna give you up", "Never gonna give you up", list(MODE_HEADSET = TRUE, MODE_SING = TRUE)) test(".s Gun plz", "Gun plz", list(RADIO_KEY = RADIO_KEY_SECURITY, RADIO_EXTENSION = RADIO_CHANNEL_SECURITY)) test("...What", "...What", list()) - //note to lettern: add the ++, ||, __, and the verb*text checks /datum/unit_test/get_message_mods/proc/test(message, expected_message, list/expected_mods) var/list/mods = list() diff --git a/code/modules/unit_tests/serving_tray.dm b/code/modules/unit_tests/serving_tray.dm new file mode 100644 index 0000000000..00e911ae50 --- /dev/null +++ b/code/modules/unit_tests/serving_tray.dm @@ -0,0 +1,47 @@ +/** + * Check that standard food items fit on the serving tray + */ +/datum/unit_test/servingtray/Run() + var/mob/living/carbon/human/human = allocate(/mob/living/carbon/human) + var/obj/structure/table/the_table = allocate(/obj/structure/table) + var/obj/item/storage/bag/tray/test_tray = allocate(/obj/item/storage/bag/tray) + var/obj/item/reagent_containers/food/banana = allocate(/obj/item/food/rationpack) + var/obj/item/food/the_bread = allocate(/obj/item/food/breadslice) + var/obj/item/reagent_containers/food/sugarcookie = allocate(/obj/item/food/cookie/sugar) + var/obj/item/clothing/under/jumpsuit = allocate(/obj/item/clothing/under/color/black) + + TEST_ASSERT_EQUAL((the_bread in test_tray.contents), FALSE, "The bread is on the serving tray at test start") + + // set the tray to single item mode the dirty way + var/datum/component/storage/tray_storage = test_tray.GetComponent(/datum/component/storage) + tray_storage.collection_mode = COLLECT_ONE + + test_tray.pre_attack(the_bread, human) + + TEST_ASSERT_EQUAL((the_bread in test_tray.contents), TRUE, "The bread did not get picked up by the serving tray") + + test_tray.pre_attack(banana, human) + + TEST_ASSERT_EQUAL((banana in test_tray.contents), TRUE, "The banana did not get picked up by the serving tray") + + the_table.attackby(test_tray, human) + + TEST_ASSERT_EQUAL(test_tray.contents.len, 0, "The serving tray did not drop all items on hitting the table") + + test_tray.pre_attack(sugarcookie, human) + + TEST_ASSERT_EQUAL((sugarcookie in test_tray.contents), TRUE, "The sugarcookie did not get picked up by the serving tray") + + human.equip_to_slot(jumpsuit, ITEM_SLOT_ICLOTHING) + TEST_ASSERT(human.get_item_by_slot(ITEM_SLOT_ICLOTHING), "Human does not have jumpsuit on") + + human.equip_to_slot(test_tray, ITEM_SLOT_LPOCKET) + TEST_ASSERT(human.get_item_by_slot(ITEM_SLOT_LPOCKET), "Serving tray failed to fit in the Left Pocket") + + human.equip_to_slot(test_tray, ITEM_SLOT_RPOCKET) + TEST_ASSERT(human.get_item_by_slot(ITEM_SLOT_RPOCKET), "Serving tray failed to fit in the Right Pocket") + + test_tray.attack(human, human) + + TEST_ASSERT_EQUAL(test_tray.contents.len, 0, "The serving tray did not drop all items on hitting a human") + diff --git a/code/modules/unit_tests/siunit.dm b/code/modules/unit_tests/siunit.dm new file mode 100644 index 0000000000..3a7a25a98d --- /dev/null +++ b/code/modules/unit_tests/siunit.dm @@ -0,0 +1,15 @@ +/datum/unit_test/siunit/Run() + TEST_ASSERT_EQUAL(siunit(0.5345, "A", 0), "535 mA", "") + TEST_ASSERT_EQUAL(siunit(0.5344, "A", 0), "534 mA", "") + TEST_ASSERT_EQUAL(siunit(-0.5344, "A", 0), "-534 mA", "") + TEST_ASSERT_EQUAL(siunit_pressure(1.234, 1), "1.2 kPa", "") // test for pascal require *10e-3, as the game thinks in kPa, the proc siunit in Pa + TEST_ASSERT_EQUAL(siunit_pressure(1.234, 2), "1.23 kPa", "") + TEST_ASSERT_EQUAL(siunit_pressure(1.234, 3), "1.234 kPa", "") + TEST_ASSERT_EQUAL(siunit_pressure(1, 4), "1 kPa", "") + TEST_ASSERT_EQUAL(siunit_pressure(0), "0 Pa", "") + TEST_ASSERT_EQUAL(siunit_pressure(1e3), "1 MPa", "") + TEST_ASSERT_EQUAL(siunit_pressure(999e3), "999 MPa", "") + TEST_ASSERT_EQUAL(siunit_pressure(999.9e3), "999.9 MPa" , "") + TEST_ASSERT_EQUAL(siunit_pressure(999.9e3, 0), "1 GPa", "") + TEST_ASSERT_EQUAL(siunit_pressure(1e6), "1 GPa", "") + TEST_ASSERT_EQUAL(siunit_pressure(3e17), "300000 PPa", "") diff --git a/code/modules/unit_tests/spawn_humans.dm b/code/modules/unit_tests/spawn_humans.dm index 7189e87277..0500deae0a 100644 --- a/code/modules/unit_tests/spawn_humans.dm +++ b/code/modules/unit_tests/spawn_humans.dm @@ -1,7 +1,7 @@ /datum/unit_test/spawn_humans/Run() - var/locs = block(run_loc_bottom_left, run_loc_top_right) + var/locs = block(run_loc_bottom_left, run_loc_top_right) - for(var/I in 1 to 5) - new /mob/living/carbon/human(pick(locs)) + for(var/I in 1 to 5) + new /mob/living/carbon/human(pick(locs)) - sleep(50) + sleep(50) diff --git a/code/modules/unit_tests/species_whitelists.dm b/code/modules/unit_tests/species_whitelists.dm new file mode 100644 index 0000000000..145f3a259f --- /dev/null +++ b/code/modules/unit_tests/species_whitelists.dm @@ -0,0 +1,5 @@ +/datum/unit_test/species_whitelist_check/Run() + for(var/typepath in subtypesof(/datum/species)) + var/datum/species/S = typepath + if(initial(S.changesource_flags) == NONE) + Fail("A species type was detected with no changesource flags: [S]") diff --git a/code/modules/unit_tests/stomach.dm b/code/modules/unit_tests/stomach.dm new file mode 100644 index 0000000000..06fdc71dd4 --- /dev/null +++ b/code/modules/unit_tests/stomach.dm @@ -0,0 +1,40 @@ +/datum/unit_test/stomach/Run() + + // Pause natural mob life so it can be handled entirely by the test + SSmobs.pause() + + var/mob/living/carbon/human/human = allocate(/mob/living/carbon/human) + var/obj/item/food/hotdog/debug/fooditem = allocate(/obj/item/food/hotdog/debug) + var/obj/item/organ/stomach/belly = human.getorganslot(ORGAN_SLOT_STOMACH) + var/obj/item/reagent_containers/pill/pill = allocate(/obj/item/reagent_containers/pill) + var/datum/reagent/drug/methamphetamine/meth = /datum/reagent/drug/methamphetamine + + TEST_ASSERT_EQUAL(human.has_reagent(/datum/reagent/consumable/ketchup), FALSE, "Human somehow has ketchup before eating") + + fooditem.attack(human, human) + + TEST_ASSERT(belly.reagents.has_reagent(/datum/reagent/consumable/ketchup), "Stomach doesn't have ketchup after eating") + TEST_ASSERT_EQUAL(human.reagents.has_reagent(/datum/reagent/consumable/ketchup), FALSE, "Human body has ketchup after eating it should only be in the stomach") + + //Give them meth and let it kick in + pill.reagents.add_reagent(meth, initial(meth.metabolization_rate) * 1.9) + pill.attack(human, human) + human.Life() + + TEST_ASSERT(human.reagents.has_reagent(meth), "Human body does not have meth after life tick") + TEST_ASSERT(human.has_movespeed_modifier(/datum/movespeed_modifier/reagent/methamphetamine), "Human consumed meth, but did not gain movespeed modifier") + + belly.Remove(human) + human.reagents.remove_all(human.reagents.total_volume) + + TEST_ASSERT_EQUAL(human.has_reagent(/datum/reagent/consumable/ketchup), FALSE, "Human has reagents after clearing") + + fooditem.attack(human, human) + + TEST_ASSERT_EQUAL(human.has_reagent(/datum/reagent/consumable/ketchup), FALSE, "Human has ketchup without a stomach") + + + +/datum/unit_test/stomach/Destroy() + SSmobs.ignite() + return ..() diff --git a/code/modules/unit_tests/surgeries.dm b/code/modules/unit_tests/surgeries.dm index 7b8145ac19..6348057f79 100644 --- a/code/modules/unit_tests/surgeries.dm +++ b/code/modules/unit_tests/surgeries.dm @@ -2,14 +2,14 @@ var/mob/living/carbon/human/patient = allocate(/mob/living/carbon/human) var/mob/living/carbon/human/user = allocate(/mob/living/carbon/human) - TEST_ASSERT_EQUAL(patient.get_missing_limbs().len, 0, "Patient is somehow missing limbs before surgery") + TEST_ASSERT_EQUAL(length(patient.get_missing_limbs()), 0, "Patient is somehow missing limbs before surgery") var/datum/surgery/amputation/surgery = new(patient, BODY_ZONE_R_ARM, patient.get_bodypart(BODY_ZONE_R_ARM)) var/datum/surgery_step/sever_limb/sever_limb = new sever_limb.success(user, patient, BODY_ZONE_R_ARM, null, surgery) - TEST_ASSERT_EQUAL(patient.get_missing_limbs().len, 1, "Patient did not lose any limbs") + TEST_ASSERT_EQUAL(length(patient.get_missing_limbs()), 1, "Patient did not lose any limbs") TEST_ASSERT_EQUAL(patient.get_missing_limbs()[1], BODY_ZONE_R_ARM, "Patient is missing a limb that isn't the one we operated on") /datum/unit_test/brain_surgery/Run() @@ -27,6 +27,33 @@ TEST_ASSERT(!patient.has_trauma_type(), "Patient kept their brain trauma after brain surgery") TEST_ASSERT(patient.getOrganLoss(ORGAN_SLOT_BRAIN) < 20, "Patient did not heal their brain damage after brain surgery") +/datum/unit_test/head_transplant/Run() + var/mob/living/carbon/human/user = allocate(/mob/living/carbon/human) + var/mob/living/carbon/human/alice = allocate(/mob/living/carbon/human) + var/mob/living/carbon/human/bob = allocate(/mob/living/carbon/human) + + alice.fully_replace_character_name(null, "Alice") + bob.fully_replace_character_name(null, "Bob") + + var/obj/item/bodypart/head/alices_head = alice.get_bodypart(BODY_ZONE_HEAD) + alices_head.drop_limb() + + var/obj/item/bodypart/head/bobs_head = bob.get_bodypart(BODY_ZONE_HEAD) + bobs_head.drop_limb() + + TEST_ASSERT_EQUAL(alice.get_bodypart(BODY_ZONE_HEAD), null, "Alice still has a head after dismemberment") + TEST_ASSERT_EQUAL(alice.get_visible_name(), "Unknown", "Alice's head was dismembered, but they are not Unknown") + + TEST_ASSERT_EQUAL(bobs_head.real_name, "Bob", "Bob's head does not remember that it is from Bob") + + // Put Bob's head onto Alice's body + var/datum/surgery_step/add_prosthetic/add_prosthetic = new + user.put_in_active_hand(bobs_head) + add_prosthetic.success(user, alice, BODY_ZONE_HEAD, bobs_head) + + TEST_ASSERT(!isnull(alice.get_bodypart(BODY_ZONE_HEAD)), "Alice has no head after prosthetic replacement") + TEST_ASSERT_EQUAL(alice.get_visible_name(), "Bob", "Bob's head was transplanted onto Alice's body, but their name is not Bob") + /datum/unit_test/multiple_surgeries/Run() var/mob/living/carbon/human/user = allocate(/mob/living/carbon/human) var/mob/living/carbon/human/patient_zero = allocate(/mob/living/carbon/human) @@ -41,8 +68,6 @@ TEST_ASSERT(surgery_for_zero.step_in_progress, "Surgery on patient zero was not initiated") var/datum/surgery/organ_manipulation/surgery_for_one = new - - sleep(0.2) // if we don't have this, then the next surgery step can start *before* the previous one does, which is no good // Without waiting for the incision to complete, try to start a new surgery TEST_ASSERT(!surgery_step.initiate(user, patient_one, BODY_ZONE_CHEST, scalpel, surgery_for_one), "Was allowed to start a second surgery without the rod of asclepius") diff --git a/code/modules/unit_tests/teleporters.dm b/code/modules/unit_tests/teleporters.dm new file mode 100644 index 0000000000..fa2624adaa --- /dev/null +++ b/code/modules/unit_tests/teleporters.dm @@ -0,0 +1,10 @@ +/datum/unit_test/auto_teleporter_linking/Run() + // Put down the teleporter machinery + var/obj/machinery/teleport/hub/hub = allocate(/obj/machinery/teleport/hub) + var/obj/machinery/teleport/station/station = allocate(/obj/machinery/teleport/station, locate(run_loc_bottom_left.x + 1, run_loc_bottom_left.y, run_loc_bottom_left.z)) + var/obj/machinery/computer/teleporter/computer = allocate(/obj/machinery/computer/teleporter, locate(run_loc_bottom_left.x + 2, run_loc_bottom_left.y, run_loc_bottom_left.z)) + + TEST_ASSERT_EQUAL(hub.power_station, station, "Hub didn't link to the station") + TEST_ASSERT_EQUAL(station.teleporter_console, computer, "Station didn't link to the teleporter console") + TEST_ASSERT_EQUAL(station.teleporter_hub, hub, "Station didn't link to the hub") + TEST_ASSERT_EQUAL(computer.power_station, station, "Teleporter console didn't link to the hub") diff --git a/code/modules/unit_tests/unit_test.dm b/code/modules/unit_tests/unit_test.dm index 36b406e75e..15fe6b466c 100644 --- a/code/modules/unit_tests/unit_test.dm +++ b/code/modules/unit_tests/unit_test.dm @@ -1,9 +1,14 @@ /* + Usage: Override /Run() to run your test code + Call Fail() to fail the test (You should specify a reason) + You may use /New() and /Destroy() for setup/teardown respectively + You can use the run_loc_bottom_left and run_loc_top_right to get turfs for testing + */ GLOBAL_DATUM(current_test, /datum/unit_test) @@ -14,19 +19,33 @@ GLOBAL_VAR(test_log) //Bit of metadata for the future maybe var/list/procs_tested - //usable vars + /// The bottom left turf of the testing zone var/turf/run_loc_bottom_left + + /// The top right turf of the testing zone var/turf/run_loc_top_right + /// The type of turf to allocate for the testing zone + var/test_turf_type = /turf/open/floor/plasteel + //internal shit + var/focus = FALSE var/succeeded = TRUE var/list/allocated var/list/fail_reasons + var/static/datum/turf_reservation/turf_reservation + /datum/unit_test/New() + if (isnull(turf_reservation)) + turf_reservation = SSmapping.RequestBlockReservation(5, 5) + + for (var/turf/reserved_turf in turf_reservation.reserved_turfs) + reserved_turf.ChangeTurf(test_turf_type) + allocated = new - run_loc_bottom_left = locate(1, 1, 1) - run_loc_top_right = locate(5, 5, 1) + run_loc_bottom_left = locate(turf_reservation.bottom_left_coords[1], turf_reservation.bottom_left_coords[2], turf_reservation.bottom_left_coords[3]) + run_loc_top_right = locate(turf_reservation.top_right_coords[1], turf_reservation.top_right_coords[2], turf_reservation.top_right_coords[3]) /datum/unit_test/Destroy() //clear the test area @@ -61,7 +80,14 @@ GLOBAL_VAR(test_log) /proc/RunUnitTests() CHECK_TICK - for(var/I in subtypesof(/datum/unit_test)) + var/tests_to_run = subtypesof(/datum/unit_test) + for (var/_test_to_run in tests_to_run) + var/datum/unit_test/test_to_run = _test_to_run + if (initial(test_to_run.focus)) + tests_to_run = list(test_to_run) + break + + for(var/I in tests_to_run) var/datum/unit_test/test = new I GLOB.current_test = test diff --git a/code/modules/uplink/uplink_items/uplink_badass.dm b/code/modules/uplink/uplink_items/uplink_badass.dm index 43e5523bad..ec0ebf66d1 100644 --- a/code/modules/uplink/uplink_items/uplink_badass.dm +++ b/code/modules/uplink/uplink_items/uplink_badass.dm @@ -78,3 +78,16 @@ limited_stock = 1 cant_discount = TRUE include_modes = list(/datum/game_mode/nuclear, /datum/game_mode/nuclear/clown_ops) + +/datum/uplink_item/badass/gaming_cardpack + name = "TCG Card Operatives Bundle" + desc = "A bundle full of goodies required to work as a TCG Card Operative. A warm pajama, a mug of cocoa, a plushie and a two packs full of rare 2560 Core Set cards!" + item = /obj/item/storage/box/syndie_kit/sleepytime/cardpack + cost = 20 + include_modes = list(/datum/game_mode/nuclear, /datum/game_mode/nuclear/clown_ops) + +/datum/uplink_item/badass/cardpack + name = "TCG Nuclear Cardpack" + desc = "A cardpack filled with top-tier TCG cards." + item = /obj/item/cardpack/syndicate + cost = 4 diff --git a/code/modules/uplink/uplink_items/uplink_clothing.dm b/code/modules/uplink/uplink_items/uplink_clothing.dm index 6163e5722a..de15b16b68 100644 --- a/code/modules/uplink/uplink_items/uplink_clothing.dm +++ b/code/modules/uplink/uplink_items/uplink_clothing.dm @@ -104,3 +104,10 @@ desc = "An eyepatch that connects itself to your eye socket, enhancing your shooting to an impossible degree, allowing your bullets to ricochet far more often than usual." item = /obj/item/clothing/glasses/eyepatch/syndicate cost = 8 + +/datum/uplink_item/device_tools/ablative_armwraps + name = "Ablative Armwraps" + desc = "A pair of highly reinforced armwraps allowing the user to parry almost anything. Fully reflects projectiles, no downsides to failing, but is very hard to parry melee with." + cost = 6 + item = /obj/item/clothing/gloves/fingerless/ablative + exclude_modes = list(/datum/game_mode/nuclear) diff --git a/code/modules/uplink/uplink_items/uplink_dangerous.dm b/code/modules/uplink/uplink_items/uplink_dangerous.dm index 898b716775..a8ca068ad2 100644 --- a/code/modules/uplink/uplink_items/uplink_dangerous.dm +++ b/code/modules/uplink/uplink_items/uplink_dangerous.dm @@ -118,6 +118,18 @@ /datum/uplink_item/dangerous/doublesword/get_discount() return pick(4;0.8,2;0.65,1;0.5) +/datum/uplink_item/dangerous/hyperblade + name = "Hypereutactic Blade" + desc = "The result of two Dragon Tooth swords combining, you wouldn't want to see this coming at you down the hall! \ + Requires two hands to wield and it slows you down. You can also recolor it!" + item = /obj/item/dualsaber/hypereutactic + player_minimum = 25 + cost = 16 + exclude_modes = list(/datum/game_mode/nuclear/clown_ops, /datum/game_mode/traitor/internal_affairs) + +/datum/uplink_item/dangerous/hyperblade/get_discount() + return pick(4;0.8,2;0.65,1;0.5) + /datum/uplink_item/dangerous/sword name = "Energy Sword" desc = "The energy sword is an edged weapon with a blade of pure energy. The sword is small enough to be \ diff --git a/code/modules/uplink/uplink_items/uplink_stealth.dm b/code/modules/uplink/uplink_items/uplink_stealth.dm index ff6d66a483..1bd75fa2b1 100644 --- a/code/modules/uplink/uplink_items/uplink_stealth.dm +++ b/code/modules/uplink/uplink_items/uplink_stealth.dm @@ -102,6 +102,7 @@ along with slurred speech, aggression, and the ability to infect others with this agent." item = /obj/item/storage/box/syndie_kit/romerol cost = 25 + player_minimum = 25 cant_discount = TRUE exclude_modes = list(/datum/game_mode/nuclear) @@ -115,7 +116,7 @@ cost = 4 exclude_modes = list(/datum/game_mode/nuclear, /datum/game_mode/nuclear/clown_ops) -datum/uplink_item/stealthy_weapons/taeclowndo_shoes +/datum/uplink_item/stealthy_weapons/taeclowndo_shoes name = "Tae-clown-do Shoes" desc = "A pair of shoes for the most elite agents of the honkmotherland. They grant the mastery of taeclowndo with some honk-fu moves as long as they're worn." cost = 12 diff --git a/code/modules/vending/_vending.dm b/code/modules/vending/_vending.dm index a115300085..5f39158413 100644 --- a/code/modules/vending/_vending.dm +++ b/code/modules/vending/_vending.dm @@ -162,10 +162,6 @@ IF YOU MODIFY THE PRODUCTS LIST OF A MACHINE, MAKE SURE TO UPDATE ITS RESUPPLY C ///Name of lighting mask for the vending machine var/light_mask -/obj/item/circuitboard - ///determines if the circuit board originated from a vendor off station or not. - var/onstation = TRUE - /** * Initialize the vending machine * @@ -559,6 +555,9 @@ GLOBAL_LIST_EMPTY(vending_products) if(crit_case) L.apply_damage(squish_damage, forced=TRUE) + if(L.stat == DEAD && L.client) + L.client.give_award(/datum/award/achievement/misc/vendor_squish, L) // good job losing a fight with an inanimate object idiot + L.Paralyze(60) L.emote("scream") playsound(L, 'sound/effects/blobattack.ogg', 40, TRUE) @@ -687,6 +686,7 @@ GLOBAL_LIST_EMPTY(vending_products) . = list() .["onstation"] = onstation .["department"] = payment_department + .["jobDiscount"] = VENDING_DISCOUNT .["product_records"] = list() for (var/datum/data/vending_product/R in product_records) var/list/data = list( @@ -713,7 +713,7 @@ GLOBAL_LIST_EMPTY(vending_products) var/list/data = list( path = replacetext(replacetext("[R.product_path]", "/obj/item/", ""), "/", "-"), name = R.name, - price = R.custom_premium_price || extra_price, //may cause breakage. please note + price = R.custom_premium_price || extra_price, max_amount = R.max_amount, ref = REF(R), premium = TRUE @@ -722,21 +722,20 @@ GLOBAL_LIST_EMPTY(vending_products) /obj/machinery/vending/ui_data(mob/user) . = list() - var/mob/living/carbon/human/H var/obj/item/card/id/C - if(ishuman(user)) - H = user - C = H.get_idcard(TRUE) - if(C?.registered_account) - .["user"] = list() - .["user"]["name"] = C.registered_account.account_holder - .["user"]["cash"] = C.registered_account.account_balance - if(C.registered_account.account_job) - .["user"]["job"] = C.registered_account.account_job.title - .["user"]["department"] = C.registered_account.account_job.paycheck_department - else - .["user"]["job"] = "No Job" - .["user"]["department"] = "No Department" + if(isliving(user)) + var/mob/living/L = user + C = L.get_idcard(TRUE) + if(C?.registered_account) + .["user"] = list() + .["user"]["name"] = C.registered_account.account_holder + .["user"]["cash"] = C.registered_account.account_balance + if(C.registered_account.account_job) + .["user"]["job"] = C.registered_account.account_job.title + .["user"]["department"] = C.registered_account.account_job.paycheck_department + else + .["user"]["job"] = "No Job" + .["user"]["department"] = "No Department" .["stock"] = list() for (var/datum/data/vending_product/R in product_records + coin_records + hidden_records) .["stock"][R.name] = R.amount diff --git a/code/modules/vending/autodrobe.dm b/code/modules/vending/autodrobe.dm index b4490f715b..264d262a1d 100644 --- a/code/modules/vending/autodrobe.dm +++ b/code/modules/vending/autodrobe.dm @@ -120,7 +120,14 @@ /obj/item/clothing/ears/headphones = 2, /obj/item/clothing/head/wig/random = 3, /obj/item/clothing/suit/ran = 2, - /obj/item/clothing/head/ran = 2) + /obj/item/clothing/head/ran = 2, + /obj/item/clothing/mask/gas/timidcostume = 3, + /obj/item/clothing/suit/hooded/wintercoat/timidcostume = 3, + /obj/item/clothing/shoes/timidcostume = 3, + /obj/item/clothing/mask/gas/timidcostume/man = 3, + /obj/item/clothing/suit/hooded/wintercoat/timidcostume/man = 3, + /obj/item/clothing/shoes/timidcostume/man = 3, + ) contraband = list(/obj/item/clothing/suit/judgerobe = 1, /obj/item/clothing/head/powdered_wig = 1, /obj/item/gun/magic/wand = 2, diff --git a/code/modules/vending/clothesmate.dm b/code/modules/vending/clothesmate.dm index 462d4b5cb5..fedaeff98c 100644 --- a/code/modules/vending/clothesmate.dm +++ b/code/modules/vending/clothesmate.dm @@ -21,6 +21,7 @@ /obj/item/clothing/suit/jacket/puffer/vest = 4, /obj/item/clothing/suit/jacket/puffer = 4, /obj/item/clothing/suit/hooded/cloak/david = 4, + /obj/item/clothing/neck/cancloak/polychromic = 4, /obj/item/clothing/suit/bomber = 5, /obj/item/clothing/under/suit/turtle/teal = 3, /obj/item/clothing/under/suit/turtle/grey = 3, @@ -31,9 +32,26 @@ /obj/item/clothing/under/suit/burgundy = 3, /obj/item/clothing/under/suit/charcoal = 3, /obj/item/clothing/under/suit/white = 3, + /obj/item/clothing/under/suit/tan = 3, + /obj/item/clothing/under/suit/charismatic_suit = 3, /obj/item/clothing/under/costume/kilt = 3, + /obj/item/clothing/suit/suspenders = 3, /obj/item/clothing/under/misc/overalls = 3, /obj/item/clothing/under/suit/sl = 3, + /obj/item/clothing/accessory/sweater = 3, + /obj/item/clothing/accessory/sweater/pink = 3, + /obj/item/clothing/accessory/sweater/heart = 3, + /obj/item/clothing/accessory/sweater/blue = 3, + /obj/item/clothing/accessory/sweater/nt = 3, + /obj/item/clothing/accessory/sweater/mint = 3, + /obj/item/clothing/accessory/sweater/shoulderless = 3, + /obj/item/clothing/accessory/sweater/flower = 3, + /obj/item/clothing/accessory/turtleneck = 2, + /obj/item/clothing/accessory/turtleneck/red = 2, + /obj/item/clothing/accessory/turtleneck/comfy = 2, + /obj/item/clothing/accessory/turtleneck/tactifool = 2, + /obj/item/clothing/accessory/turtleneck/tactifool/green = 2, + /obj/item/clothing/accessory/turtleneck/tactifool/blue = 2, /obj/item/clothing/under/sweater = 3, /obj/item/clothing/under/sweater/black = 3, /obj/item/clothing/under/sweater/purple = 3, @@ -50,7 +68,14 @@ /obj/item/clothing/under/pants/black = 4, /obj/item/clothing/under/pants/tan = 4, /obj/item/clothing/under/pants/track = 3, + /obj/item/clothing/under/pants/polypants/polychromic = 4, + /obj/item/clothing/accessory/suitjacket = 2, + /obj/item/clothing/accessory/suitjacket/charcoal = 2, + /obj/item/clothing/accessory/suitjacket/navy = 2, + /obj/item/clothing/accessory/suitjacket/burgundy = 2, + /obj/item/clothing/accessory/suitjacket/checkered = 2, /obj/item/clothing/suit/jacket/miljacket = 5, + /obj/item/clothing/suit/jacket/urbanjacket/polychromic = 5, /obj/item/clothing/under/suit/white_on_white/skirt = 2, /obj/item/clothing/under/rank/captain/suit/skirt = 2, /obj/item/clothing/under/rank/civilian/head_of_personnel/suit/skirt = 2, @@ -74,6 +99,12 @@ /obj/item/clothing/neck/stripedbluescarf = 3, /obj/item/clothing/neck/stripedgreenscarf = 3, /obj/item/clothing/accessory/waistcoat = 2, + /obj/item/clothing/accessory/waistcoat/red = 2, + /obj/item/clothing/accessory/waistcoat/grey = 2, + /obj/item/clothing/accessory/waistcoat/brown = 2, + /obj/item/clothing/accessory/waistcoat/sweatervest = 2, + /obj/item/clothing/accessory/waistcoat/sweatervest/blue = 2, + /obj/item/clothing/accessory/waistcoat/sweatervest/red = 2, /obj/item/clothing/under/dress/skirt = 3, /obj/item/clothing/under/dress/skirt/blue = 3, /obj/item/clothing/under/dress/skirt/red = 3, @@ -132,22 +163,22 @@ /obj/item/clothing/ears/headphones = 10, /obj/item/clothing/suit/apron/purple_bartender = 4, /obj/item/clothing/under/rank/civilian/bartender/purple = 4, + /* Commenting out until next Christmas or made automatic + /obj/item/clothing/accessory/sweater/uglyxmas = 3, /obj/item/clothing/under/costume/christmas = 3, /obj/item/clothing/under/costume/christmas/green = 3, /obj/item/clothing/under/costume/christmas/croptop = 3, /obj/item/clothing/under/costume/christmas/croptop/green = 3, - */ /obj/item/clothing/suit/hooded/wintercoat/christmascoatr = 3, /obj/item/clothing/suit/hooded/wintercoat/christmascoatg = 3, /obj/item/clothing/suit/hooded/wintercoat/christmascoatrg = 3, - /*Commenting out until next Christmas or made automatic /obj/item/clothing/head/christmashat = 3, /obj/item/clothing/head/christmashatg = 3, - */ /obj/item/clothing/shoes/winterboots/christmasbootsr = 3, /obj/item/clothing/shoes/winterboots/christmasbootsg = 3, /obj/item/clothing/shoes/winterboots/santaboots = 3, + */ /obj/item/clothing/head/cowboyhat = 3, /obj/item/clothing/head/cowboyhat/black = 3, /obj/item/clothing/head/cowboyhat/white = 3, @@ -161,7 +192,8 @@ /obj/item/clothing/under/costume/cheongsam/white = 3, /obj/item/clothing/under/costume/cheongsam/red = 3, /obj/item/storage/backpack/snail = 3) - contraband = list(/obj/item/clothing/under/syndicate/tacticool = 3, + contraband = list(/obj/item/clothing/accessory/turtleneck/tactifool/syndicate = 3, + /obj/item/clothing/under/syndicate/tacticool = 3, /obj/item/clothing/under/syndicate/tacticool/skirt = 3, /obj/item/clothing/mask/balaclava = 3, /obj/item/clothing/head/ushanka = 3, @@ -182,7 +214,9 @@ /obj/item/clothing/neck/necklace/dope = 5, /obj/item/clothing/suit/jacket/letterman_nanotrasen = 5, /obj/item/clothing/under/misc/corporateuniform = 5, - /obj/item/clothing/suit/hooded/wintercoat/polychromic = 5) + /obj/item/clothing/suit/hooded/wintercoat/polychromic = 5, + /obj/item/clothing/suit/toggle/wbreakpoly/polychromic = 5, + /obj/item/clothing/shoes/sneakers/poly/polychromic = 10) refill_canister = /obj/item/vending_refill/clothing default_price = PRICE_CHEAP extra_price = PRICE_BELOW_NORMAL diff --git a/code/modules/vending/games.dm b/code/modules/vending/games.dm index 6c29b412e7..cea9c5ae70 100644 --- a/code/modules/vending/games.dm +++ b/code/modules/vending/games.dm @@ -7,7 +7,9 @@ /obj/item/storage/dice = 10, /obj/item/toy/cards/deck/cas = 3, /obj/item/toy/cards/deck/cas/black = 3, - /obj/item/toy/cards/deck/unum = 3) + /obj/item/toy/cards/deck/unum = 3, + /obj/item/cardpack/series_one = 10, + /obj/item/tcgcard_binder = 5) contraband = list(/obj/item/dice/fudge = 9) premium = list(/obj/item/melee/skateboard/pro = 3, /obj/item/melee/skateboard/hoverboard = 1) diff --git a/code/modules/vending/kinkmate.dm b/code/modules/vending/kinkmate.dm index e522583772..28e5a950ad 100644 --- a/code/modules/vending/kinkmate.dm +++ b/code/modules/vending/kinkmate.dm @@ -2,7 +2,6 @@ name = "KinkMate" desc = "A vending machine for all your unmentionable desires." icon_state = "kink" - circuit = /obj/item/circuitboard/machine/kinkmate product_slogans = "Kinky!;Sexy!;Check me out, big boy!" vend_reply = "Have fun, you shameless pervert!" products = list( @@ -13,6 +12,7 @@ /obj/item/clothing/neck/petcollar = 5, /obj/item/clothing/neck/petcollar/choker = 5, /obj/item/clothing/neck/petcollar/leather = 5, + /obj/item/clothing/neck/necklace/cowbell = 5, /obj/item/restraints/handcuffs/fake/kinky = 5, /obj/item/clothing/glasses/sunglasses/blindfold = 4, /obj/item/clothing/mask/muzzle = 4, @@ -31,6 +31,8 @@ /obj/item/autosurgeon/testicles = 3, /obj/item/storage/pill_bottle/penis_enlargement = 5, /obj/item/storage/pill_bottle/breast_enlargement = 5, + /obj/item/reagent_containers/glass/bottle/crocin = 5, + /obj/item/reagent_containers/glass/bottle/camphor = 5, /obj/item/storage/daki = 4 ) contraband = list( @@ -46,6 +48,7 @@ ) premium = list( /obj/item/clothing/accessory/skullcodpiece/fake = 3, + /obj/item/reagent_containers/glass/bottle/hexacrocin = 10, /obj/item/clothing/under/pants/chaps = 5 ) refill_canister = /obj/item/vending_refill/kink diff --git a/code/modules/vending/medical.dm b/code/modules/vending/medical.dm index 795d35adc4..a24233b17c 100644 --- a/code/modules/vending/medical.dm +++ b/code/modules/vending/medical.dm @@ -34,7 +34,8 @@ /obj/item/healthanalyzer/wound = 4, /obj/item/stack/medical/ointment = 2, /obj/item/stack/medical/suture = 2, - /obj/item/stack/medical/bone_gel = 4) + /obj/item/stack/medical/bone_gel = 4, + /obj/item/stack/medical/nanogel = 4) contraband = list(/obj/item/reagent_containers/pill/tox = 3, /obj/item/reagent_containers/pill/morphine = 4, /obj/item/reagent_containers/pill/charcoal = 6) diff --git a/code/modules/vending/medical_wall.dm b/code/modules/vending/medical_wall.dm index 31f3dc49f1..2d4c30080d 100644 --- a/code/modules/vending/medical_wall.dm +++ b/code/modules/vending/medical_wall.dm @@ -13,6 +13,7 @@ /obj/item/reagent_containers/medspray/sterilizine = 1, /obj/item/healthanalyzer/wound = 2, /obj/item/stack/medical/bone_gel = 2, + /obj/item/stack/medical/nanogel = 2, /obj/item/reagent_containers/syringe/dart = 10) contraband = list(/obj/item/reagent_containers/pill/tox = 2, /obj/item/reagent_containers/pill/morphine = 2) diff --git a/code/modules/vending/robotics.dm b/code/modules/vending/robotics.dm index 88f65506a6..2d77b2fc51 100644 --- a/code/modules/vending/robotics.dm +++ b/code/modules/vending/robotics.dm @@ -17,7 +17,8 @@ /obj/item/tank/internals/anesthetic = 2, /obj/item/clothing/mask/breath/medical = 5, /obj/item/screwdriver = 5, - /obj/item/crowbar = 5) + /obj/item/crowbar = 6, + /obj/item/stack/medical/nanogel = 5) armor = list("melee" = 100, "bullet" = 100, "laser" = 100, "energy" = 100, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50) resistance_flags = FIRE_PROOF default_price = PRICE_EXPENSIVE diff --git a/code/modules/vending/wardrobes.dm b/code/modules/vending/wardrobes.dm index 482cdb7d86..eebb07938b 100644 --- a/code/modules/vending/wardrobes.dm +++ b/code/modules/vending/wardrobes.dm @@ -207,12 +207,13 @@ product_ads = "You turn me TRUE, use defines!;0110001101101100011011110111010001101000011001010111001101101000011001010111001001100101" vend_reply = "Thank you for using the RoboDrobe!" products = list(/obj/item/clothing/glasses/hud/diagnostic = 3, + /obj/item/stack/medical/nanogel = 5, /obj/item/clothing/head/beret/robo = 3, /obj/item/clothing/under/rank/rnd/roboticist = 3, /obj/item/clothing/under/rank/rnd/roboticist/sleek = 3, /obj/item/clothing/under/rank/rnd/roboticist/skirt = 3, /obj/item/clothing/suit/hooded/wintercoat/robotics = 3, - /obj/item/clothing/suit/toggle/labcoat = 3, + /obj/item/clothing/suit/toggle/labcoat/roboticist = 3, /obj/item/clothing/shoes/sneakers/black = 3, /obj/item/clothing/gloves/fingerless = 3, /obj/item/clothing/head/soft/black = 3, diff --git a/code/modules/vore/eating/belly_obj.dm b/code/modules/vore/eating/belly_obj.dm index 7f8aed83ef..8e74dd3c92 100644 --- a/code/modules/vore/eating/belly_obj.dm +++ b/code/modules/vore/eating/belly_obj.dm @@ -248,7 +248,7 @@ for(var/mob/living/H in hearing_mobs) if(H && H.client && (isturf(H.loc) || (H.loc != src.contents))) var/sound/releasement = GLOB.pred_release_sounds[release_sound] - H.playsound_local(owner.loc, releasement, vol = 75, vary = 1, falloff = VORE_SOUND_FALLOFF) + H.playsound_local(owner.loc, releasement, 75, TRUE) else if(H?.client && (H in contents)) var/sound/releasement = GLOB.prey_release_sounds[release_sound] SEND_SOUND(H,releasement) @@ -304,7 +304,7 @@ for(var/mob/living/H in hearing_mobs) if(H && H.client && (isturf(H.loc) || (H.loc != src.contents))) var/sound/releasement = GLOB.pred_release_sounds[release_sound] - H.playsound_local(owner.loc, releasement, vol = 75, vary = 1, falloff = VORE_SOUND_FALLOFF) + H.playsound_local(owner.loc, releasement, 75, TRUE) else if(H?.client && (H in contents)) var/sound/releasement = GLOB.prey_release_sounds[release_sound] SEND_SOUND(H,releasement) @@ -366,7 +366,7 @@ for(var/mob/living/H in hearing_mobs) if(H && H.client && (isturf(H.loc) || (H.loc != src.contents))) var/sound/eating = GLOB.pred_vore_sounds[vore_sound] - H.playsound_local(owner.loc, eating, vol = 75, vary = 1, falloff = VORE_SOUND_FALLOFF) + H.playsound_local(owner.loc, eating, 75, TRUE) else if(H?.client && (H in contents)) var/sound/eating = GLOB.prey_vore_sounds[vore_sound] SEND_SOUND(H,eating) @@ -585,14 +585,14 @@ if(is_wet) for(var/mob/living/H in hearing_mobs) if(H && H.client && (isturf(H.loc) || (H.loc != src.contents))) - H.playsound_local(owner.loc, pred_struggle_snuggle, vol = 75, vary = 1, falloff = VORE_SOUND_FALLOFF) + H.playsound_local(owner.loc, pred_struggle_snuggle, 75, TRUE) else if(H && H.client && (H in contents)) SEND_SOUND(H,prey_struggle_snuggle) else for(var/mob/living/H in hearing_mobs) if(H && H.client) - H.playsound_local(owner.loc, struggle_rustle, vol = 75, vary = 1, falloff = VORE_SOUND_FALLOFF) + H.playsound_local(owner.loc, struggle_rustle, 75, TRUE) for(var/mob/living/H in hearing_mobs) if(H && H.client && (isturf(H.loc))) diff --git a/code/modules/vore/eating/bellymodes.dm b/code/modules/vore/eating/bellymodes.dm index 291ef8654f..7cf36ebc50 100644 --- a/code/modules/vore/eating/bellymodes.dm +++ b/code/modules/vore/eating/bellymodes.dm @@ -245,7 +245,7 @@ last_hearcheck = world.time for(var/mob/M in hearing_mobs) //so we don't fill the whole room with the sound effect if(M && M.client && (isturf(M.loc) || (M.loc != src.contents))) //to avoid people on the inside getting the outside sounds and their direct sounds + built in sound pref check - M.playsound_local(owner.loc, play_sound, vol = 75, vary = 1, falloff = VORE_SOUND_FALLOFF) + M.playsound_local(owner.loc, play_sound, 75, TRUE) //these are all external sound triggers now, so it's ok. if(to_update) for(var/mob/living/M in contents) diff --git a/code/modules/vore/eating/living.dm b/code/modules/vore/eating/living.dm index a0ae58a44d..41d7da16a1 100644 --- a/code/modules/vore/eating/living.dm +++ b/code/modules/vore/eating/living.dm @@ -359,6 +359,10 @@ if(QDELETED(tasted) || (tasted.ckey && !(tasted.client?.prefs.vore_flags & LICKABLE)) || !Adjacent(tasted) || incapacitated(ignore_restraints = TRUE)) return + if(ishuman(tasted)) + var/mob/living/carbon/human/H = tasted + H.wash_cream() + visible_message("[src] licks [tasted]!","You lick [tasted]. They taste rather like [tasted.get_taste_message()].","Slurp!") /mob/living/proc/get_taste_message(allow_generic = TRUE, datum/species/mrace) diff --git a/code/modules/vore/eating/vorepanel.dm b/code/modules/vore/eating/vorepanel.dm index 6e3951e60a..5622ec0382 100644 --- a/code/modules/vore/eating/vorepanel.dm +++ b/code/modules/vore/eating/vorepanel.dm @@ -4,8 +4,8 @@ #define BELLIES_MAX 20 #define BELLIES_NAME_MIN 2 -#define BELLIES_NAME_MAX 12 -#define BELLIES_DESC_MAX 1024 +#define BELLIES_NAME_MAX 24 +#define BELLIES_DESC_MAX 4096 /mob/living/proc/insidePanel() set name = "Vore Panel" diff --git a/code/world.dm b/code/world.dm index 344ca9be79..71a9853d01 100644 --- a/code/world.dm +++ b/code/world.dm @@ -1,12 +1,22 @@ //This file is just for the necessary /world definition //Try looking in game/world.dm +/** + * # World + * + * Two possibilities exist: either we are alone in the Universe or we are not. Both are equally terrifying. ~ Arthur C. Clarke + * + * The byond world object stores some basic byond level config, and has a few hub specific procs for managing hub visiblity + * + * The world /New() is the root of where a round itself begins + */ /world mob = /mob/dead/new_player turf = /turf/open/space/basic area = /area/space view = "15x15" hub = "Exadv1.spacestation13" + hub_password = "kMZy3U5jJHSiBQjr" name = "/tg/ Station 13" fps = 20 #ifdef FIND_REF_NO_CHECK_TICK diff --git a/config/comms.txt b/config/comms.txt index 5a32f10fd3..ae336d484b 100644 --- a/config/comms.txt +++ b/config/comms.txt @@ -1,7 +1,7 @@ ## Communication key for receiving data through world/Topic(), you don't want to give this out #COMMS_KEY default_pwd -## World address and port for server recieving cross server messages +## World address and port for server receiving cross server messages ## Use '+' to denote spaces in ServerName ## Repeat this entry to add more servers #CROSS_SERVER ServerName byond:\\address:port @@ -9,9 +9,7 @@ ## Name that the server calls itself in communications #CROSS_COMMS_NAME -## Hub address for tracking stats -## example: Hubmakerckey.Hubname -#MEDAL_HUB_ADDRESS - -## Password for the hub page -#MEDAL_HUB_PASSWORD \ No newline at end of file +## Network-name used for cross-server broadcasts made from communication consoles. +## Servers that do not match this network-name will have their messages discarded. +## Leaving this commented will allow all messages through, regardless of network. +#CROSS_COMMS_NETWORK default_network diff --git a/config/config.txt b/config/config.txt index c7bb36af0e..928b8de125 100644 --- a/config/config.txt +++ b/config/config.txt @@ -367,6 +367,13 @@ NOTIFY_NEW_PLAYER_ACCOUNT_AGE 1 ## Requires database #PANIC_BUNKER +## If a player connects during a bunker with less then or this amount of living time (Minutes), we deny the connection +#PANIC_BUNKER_LIVING 90 + +## The message the Panic Bunker gives when someone is rejected by it +## %minutes% is replaced with PANIC_BUNKER_LIVING on runtime, remove it if you don't want this +#PANIC_BUNKER_MESSAGE Sorry, but the server is currently not accepting connections from players with less than %minutes% minutes of living time. + ## If panic bunker is on and a player is rejected (see above), attempt to send them to this connected server (see below) instead. ## You probably want this to be the same as CROSS_SERVER_ADDRESS #PANIC_SERVER_ADDRESS byond://address:port diff --git a/config/game_options.txt b/config/game_options.txt index 3c53d9fecb..b9d763e5b0 100644 --- a/config/game_options.txt +++ b/config/game_options.txt @@ -72,6 +72,14 @@ ALERT_DELTA Destruction of the station is imminent. All crew are instructed to o ## Uncomment to not send a roundstart intercept report. Gamemodes may override this. #NO_INTERCEPT_REPORT +## Comment to disable weighting modes by how chaotic recent mode rolls were. +WEIGH_BY_RECENT_CHAOS + +## The weight adjustment will be proportional to this power relative to the "ideal" weight range. +## e.g. if we have a weight range of 0-5, and an exponent of 1, 6 will be weighted 1/2, 7 1/3 etc. +## if exponent is 2, it'll be 1/4, 1/9 etc. +CHAOS_EXPONENT 1 + ## Probablities for game modes chosen in 'secret' and 'random' modes. ## Default probablity is 1, increase to make that mode more likely to be picked. ## Set to 0 to disable that mode. @@ -154,6 +162,11 @@ FORCE_ANTAG_COUNT CLOCKWORK_CULT #FORCE_ANTAG_COUNT WIZARD #FORCE_ANTAG_COUNT MONKEY +## A config for how much each game mode's chaos level is. +## All of them have reasonable defaults, but this can be used to adjust them. +## 0-9, where 0 is lowest chaos (should only be extended) and 9 is highest (wizard? nukies?) +#CHAOS_LEVEL EXTENDED 0 + ## Uncomment these for overrides of the minimum / maximum number of players in a round type. ## If you set any of these occasionally check to see if you still need them as the modes ## will still be actively rebalanced around the SUGGESTED populations, not your overrides. @@ -675,3 +688,6 @@ TURF_DIRT_THRESHOLD 100 ## Default alpha of dirt on spawn DIRT_ALPHA_STARTING 127 + +## Allows pAI custom holoforms +PAI_CUSTOM_HOLOFORMS diff --git a/config/policy.txt b/config/policy.txt index 610acd2be8..502b525ad0 100644 --- a/config/policy.txt +++ b/config/policy.txt @@ -3,7 +3,10 @@ ## ON_CLONE - displayed after a successful cloning operation to the cloned person ## ON_DEFIB_INTACT - displayed after defibbing before memory loss time threshold ## ON_DEFIB_LATE - displayed after defibbing post memory loss time threshold -## +## SDGF - displayed on SDGF clone spawning +## SDGF_GOOD - displayed on SDGF clone spawning, if the clone is loyal +## SDGF_BAD - displayed on SDGF clone spawning, if the clone is not loyal +## PAI - displayed on PAI personality being loaded ## EXAMPLE: ## POLICYCONFIG ON_CLONE insert text here span classes are fully supported diff --git a/dependencies.sh b/dependencies.sh index 75e49f3fe1..0fbad2153c 100644 --- a/dependencies.sh +++ b/dependencies.sh @@ -1,26 +1,24 @@ -#!/bin/bash +#!/bin/sh #Project dependencies file #Final authority on what's required to fully build the project # byond version -# Extracted from the Dockerfile. Change by editing Dockerfile's FROM command. -LIST=($(sed -n 's/.*byond:\([0-9]\+\)\.\([0-9]\+\).*/\1 \2/p' Dockerfile)) -export BYOND_MAJOR=${LIST[0]} -export BYOND_MINOR=${LIST[1]} -unset LIST +export BYOND_MAJOR=513 +export BYOND_MINOR=1536 #rust_g git tag -export RUST_G_VERSION=0.4.4 - -#bsql git tag -export BSQL_VERSION=v1.4.0.0 +export RUST_G_VERSION=0.4.7 #node version export NODE_VERSION=12 - -# PHP version -export PHP_VERSION=5.6 +export NODE_VERSION_PRECISE=12.20.0 # SpacemanDMM git tag -export SPACEMAN_DMM_VERSION=suite-1.4 +export SPACEMAN_DMM_VERSION=suite-1.6 + +# Extools git tag +export EXTOOLS_VERSION=v0.0.7 + +# Python version for mapmerge and other tools +export PYTHON_VERSION=3.6.8 diff --git a/html/admin/unbanpanel.css b/html/admin/unbanpanel.css new file mode 100644 index 0000000000..cf4aae20c9 --- /dev/null +++ b/html/admin/unbanpanel.css @@ -0,0 +1,61 @@ +body { + margin: 0; +} + +.searchbar { + overflow: hidden; + background-color: #272727; + position: fixed; + top: 0; + width: 100%; + font-weight: bold; + text-align: center; + line-height: 30px; + z-index: 1; +} + +.main { + padding: 16px; + margin-top: 20px; + text-align: center; +} + +.banbox { + position: relative; + width: 90%; + display: table; + flex-direction: column; + border: 1px solid #161616; + margin-right: auto; + margin-left: auto; + margin-bottom: 10px; + border-radius: 3px; +} + +.header { + width: 100%; + background-color:rgba(0,0,0,0.3); +} + +.container { + display: table; + width: 100%; +} + +.reason { + display: table-cell; + width: 90%; +} + +.edit { + display: table-cell; + width: 10%; +} + +.banned { + background-color:#ff5555; +} + +.unbanned { + background-color:#00b75c; +} diff --git a/html/admin/view_variables.css b/html/admin/view_variables.css index 34c1a211eb..b646e4ced1 100644 --- a/html/admin/view_variables.css +++ b/html/admin/view_variables.css @@ -3,7 +3,7 @@ body { font-size: 9pt; } .value { - font-family: "consolas", monospace; /* consolas is better!! (was "Courier New") */ + font-family: "Courier New", monospace; font-size: 8pt; display: inline-block; } @@ -12,24 +12,26 @@ table.matrix { border-collapse: collapse; border-spacing: 0; font-size: 7pt; } -table.matrix td{ +.matrix td{ text-align: center; padding: 0 1ex 0ex 1ex; } table.matrixbrak { border-collapse: collapse; border-spacing: 0; } -table.matrixbrak td.lbrak, table.matrixbrak td.rbrak{ +table.matrixbrak td.lbrak { width: 0.8ex; font-size: 50%; border-top: solid 0.25ex black; border-bottom: solid 0.25ex black; -} -table.matrixbrak td.lbrak { border-left: solid 0.5ex black; border-right: none; } table.matrixbrak td.rbrak { - border-right: solid 0.5ex black; - border-left: none; + width: 0.8ex; + font-size: 50%; + border-top: solid 0.25ex black; + border-bottom: solid 0.25ex black; + border-right: solid 0.5ex black; + border-left: none; } diff --git a/html/browser/common.css b/html/browser/common.css index 25db5313d4..eb6fed9a9e 100644 --- a/html/browser/common.css +++ b/html/browser/common.css @@ -76,7 +76,6 @@ a.icon img, .linkOn.icon img width: 18px; height: 18px; } - ul { padding: 4px 0 0 10px; @@ -396,3 +395,17 @@ ul.sparse { .slider.round:before { border-radius: 50%; } + +.severity { + margin:0px; + padding: 1px 8px 1px 8px; + border-radius: 25px; + border: 1px solid #161616; + background: #40628a; + color: #ffffff; +} + +.severity img { + display: inline-block; + vertical-align: middle; +} diff --git a/html/browser/roundend.css b/html/browser/roundend.css index e69635e888..2558d97ad6 100644 --- a/html/browser/roundend.css +++ b/html/browser/roundend.css @@ -10,13 +10,15 @@ color: #ef2f3c; font-weight: bold; } + .bluetext { color: #517fff; font-weight: bold; } + .neutraltext { font-weight: bold; /* If you feel these should have some color feel free to change */ -} +} .marooned { color: rgb(109, 109, 255); font-weight: bold; @@ -93,4 +95,4 @@ body { .tooltip_container:hover .tooltip_hover { visibility: visible; -} \ No newline at end of file +} diff --git a/html/browser/scannernew.css b/html/browser/scannernew.css index 6746a61a0d..ac1c6c2424 100644 --- a/html/browser/scannernew.css +++ b/html/browser/scannernew.css @@ -9,13 +9,20 @@ margin: 2px 2px 0 10px; text-align: center; } - .dnaBlock { font-family: Fixed, monospace; float: left; } - +a.incompleteBlock +{ + background: #8a4040; +} +a.incompleteBlock:hover +{ + color: #40628a; + background: #ffffff; +} img.selected { border: 1px solid blue; @@ -36,4 +43,4 @@ a.clean background: none; border: none; marging: none; -} \ No newline at end of file +} \ No newline at end of file diff --git a/html/changelog.html b/html/changelog.html index 8a1401744e..a04efa2a55 100644 --- a/html/changelog.html +++ b/html/changelog.html @@ -50,896 +50,1352 @@ -->
    -

    04 October 2020

    -

    DeltaFire15 updated:

    +

    13 March 2021

    +

    Hatterhat updated:

      -
    • Synths / IPCs are no longer wound immune.
    • -
    • Husked IPCs / Synths should now be rendered correctly.
    • -
    • Falling vendors now squish synths / IPCs' limbs again.
    • -
    • Synths and IPCs now do not have some fun roundstart oversights anymore.
    • -
    • Regenerate_limbs now works for carbons with the ROBOTIC_LIMBS trait.
    • -
    • Pacifists no longer counterattack on parries if that attack would be harmful.
    • -
    • Heretic sacrifices now husk with the reason of burn, and deal some additional damage.
    • -
    • Neovgre can no longer become invincible on clock tiles.
    • -
    • Plushlings no longer break when absorbing snowflake plushies.
    • +
    • BRPEDs don't examine as "not just a BRPED painted orange".
    -

    Detective-Google updated:

    +

    silicons updated:

      -
    • the snow cabin doors actually bolt now
    • +
    • paper wizard is now sentience_boss.
    • +
    + +

    12 March 2021

    +

    R3dtail updated:

    +
      +
    • Adds Periods and moves some words around.
    • +
    + +

    11 March 2021

    +

    Putnam3145 updated:

    +
      +
    • Supernova event
    • +
    • Bloodsucker day/night cycle now processes on a subsystem instead of using while and sleep (!!)
    • +
    • A chaos-weighting system for secret
    • +
    +

    Sneakyrat updated:

    +
      +
    • Fixes precise insertion ui(when it asks you how many sheets you want to put in the mat container) so you can actually choose how much to put in and it will still work if your stack is bigger than the remaining space
    • +
    • Fixes precise insertion not working for remote mat containers
    • +
    • Fixes Protolathe/mechfab insert material animations not working
    • +
    • Fixes the material insertion animation for protolathes occurring on the circuit printer
    • +
    • Fixes the material insertion animation not actually playing when it was started
    • +
    • Fixes the material insertion animation for iron not being made
    • +
    • Fixes the material insertion animation sometimes not picking the right material for sheets that have multiple different materials in them
    • +
    +

    necromanceranne updated:

    +
      +
    • Properly segments all the augment sprites so that they correctly display.
    • +
    • Properly labels the augment limb icon sprites.
    • +
    • Fixes going over the defined limit of roundstart prosthetics
    • +
    + +

    10 March 2021

    +

    Hatterhat updated:

    +
      +
    • The femur breaker now actually breaks legs by applying a compound fracture.

    Putnam3145 updated:

      -
    • Ghosts are no longer incapable of going away.
    • +
    • uncapped TEG power, buffing high-temp TEGs
    -

    monster860 updated:

    + +

    09 March 2021

    +

    LetterN updated:

      -
    • The slimeperson swap-body UI stays open when you switch bodies
    • +
    • tg hardsync, mostly contains tgui
    • +
    + +

    07 March 2021

    +

    Hatterhat updated:

    +
      +
    • You can now reskin your improvised shotguns.
    • +
    • The spontaneous brain trauma event now announces to ghosts whoever got funnied upon.
    • +
    • Ports EikoBiko's cat tail sprite.
    • +
    +

    Putnam3145 updated:

    +
      +
    • nitryl now consumes oxygen/nitrogen instead of generating them
    • +
    • Hyper-nob and nitryl are easier to make.
    • +
    +

    dzahlus updated:

    +
      +
    • Added taser microbattery for MWS-01
    • +
    • tweaked MWS-01 beacondrop to have more batteries
    • +
    • rebalanced MWS-01 disabler battery to fire 10 shots
    • +
    • added unique sound to the MWS-01
    • +
    • fixed Modula Weapons System to "Modular Weapon System"

    timothyteakettle updated:

      -
    • limb id entry in mutant bodyparts now supports switching to/from species with gendered body parts
    • -
    • the minimum brightness of mutant parts is now a define
    • +
    • exiting a bluespace jar through any means, hardstuns you for 5 seconds
    -

    02 October 2020

    -

    ArcaneMusic, with minor tweaks by TheObserver-sys updated:

    +

    05 March 2021

    +

    Putnam3145 updated:

      -
    • Adds and modifies fertilizers, as well as a new stat, Instability.
    • -
    • Removes the rather disused functionality of irrigation hoses, temporarily disables circuit use on hydroponics trays.
    • -
    • Plant Analyzer have been upgraded. Using one in hand will now switch between a stat view of your plant, and a chemical view of your plant. tweak:Trays now accept and store reagents, as well as coming with an autogrow mode. However, these upgrades came at the cost of old self sufficiency, meaning you must attend to your plants a bit more often.
    • -
    • Earthsblood, while not being able to gild trays anymore, has been found to still be quite powerful as a fertilizer.
    • +
    • Lowered ash storm volume
    • +
    • Minesweeper can no longer be made to lag the server on purpose
    -

    CoreFlare updated:

    +

    keronshb updated:

      -
    • Altcloaks! Available in loadout.
    • -
    -

    Detective-Google updated:

    -
      -
    • a smattering of clothes from the RP server
    • -
    • detective wardrobe
    • -
    -

    EmeraldSundisk updated:

    -
      -
    • Adds the "Skelter" space ruin
    • -
    • Creates a few new area designations for the Skelter
    • -
    • Cargo techs now have access to a "long pants" variant of their standard work uniform
    • -
    • Adds said uniform to CargoDrobes and the loadout menu
    • -
    -

    ItzGabby updated:

    -
      -
    • Three new turf tiles.
    • -
    • Turf icons with multiple damage icons, with in-hand icons for each tile.
    • -
    • Edited the name and description to wooden.
    • +
    • Prevents heat from going through reinforced plasma glass.
    + +

    04 March 2021

    LetterN updated:

      -
    • craftable railings
    • -
    • ports robust savefiles
    • +
    • removes bsql
    -

    MrJWhit updated:

    + +

    03 March 2021

    +

    MarinaGryphon updated:

      -
    • tweaked heavy suit dmi
    • -
    • Fixes drones not being able to quickslot items
    • +
    • The AOOC mute pref is now properly respected.
    • +
    • Muting adminhelp sounds no longer mutes AOOC.

    Putnam3145 updated:

      -
    • New policy config for pyroclastic slimes.
    • -
    • SDGF clones now naked.
    • -
    • SDGF is now way more likely to make a clone that will align with the creator's goals, with purity, making it a better antagging tool.
    • -
    • SDGF clones now have the same traits as the original.
    • -
    • transfer_ckey now resets view, preventing things like SDGF clones with much larger view range.
    • -
    • Policy configs have been added for SDGF, currently unused: SDGF, SDGF_ALIGNED, SDGF_UNALIGNED.
    • -
    • Shivering now has thresholds and fever's thresholds now work.
    • -
    • Made allturfs setup actually set up all turfs.
    • -
    • Dynamic is now more aggressive with adding antags.
    • -
    • Fixes vore pref saving.
    • +
    • pAIs now have a policy config
    • +
    • "Supermatter surge" event, which might cause problems if the supermatter is not sufficiently cooled (i.e. the setup is messed up in some way)
    • +
    • Fusion can no longer be done in open air.
    • +
    • Valentine's day event no longer gives everyone a valentine's antag.

    SandPoot updated:

      -
    • Fixes headslugs being unable to recover their human form.
    • +
    • Legions should now pass their type to the person they infect (if valid).
    -

    Tupinambis updated:

    +

    dzahlus updated:

      -
    • Adds methane and methyl bromide gases.
    • -
    • Minor gas name/desc changes to improve consistency
    • -
    • Ports the methyl bromide tank from bay. Adds two new canister sprites for the new gases and CH4 screen alerts.
    • -
    • fixed a mispelling in the wound armor value for the bounty hunter suit
    • +
    • Added new subtype to lesser ash drake balanced around player control
    • +
    • rebalanced dragon transformation to a 1 minute cooldown as well as using the new subtype of megafauna
    -

    dapnee updated:

    +

    qweq12yt updated:

      -
    • atrium, clinic, an extra office, a pseudo public mining area, two more deluxe dorms, micro beach, aux bathroom, two construction areas, mass driver, more intercoms
    • -
    • moved all of service, chapel, dorms, garden, holodeck to a different z-level, RnD is more open, maintenance is a bit more random, more firelocks, added more mine-able rock
    • -
    • fixed the buttons in xenobio, gave shutters to cargo's storage area, fixed holodeck so it works now, fixed some techfabs being lathes, added sensors to atmos tanks, more decals, couple more signs, little floral areas and sitting areas added to break up hallway monotony, mech chargers are no longer missing their consoles, whiteship won't crash into arrivals anymore while the area it takes up is more telegraphed, APC placement on AI sat entrance, missing wire node for the outer portion of the AI sat, civilian level now has a telecom relay
    • -
    -

    lolman360 updated:

    -
      -
    • smonk machine runtimes
    • -
    • automatic hydro tray has a unique sprite now (fancy robot arm)
    • -
    -

    timothyteakettle updated:

    -
      -
    • added luminescent and stargazer sprites as selectable body sprites for slimes
    • -
    • wound exponent lowered slightly from 1.225 to 1.2
    • -
    • wound exponent and limb damage multiplier are now config values
    • -
    • ipcs and synthlizards are now treated as actual robots, with robotic limbs, an extra organ, and better emp acts
    • -
    • surgeries for healing robotic limbs, and brain surgery for robotic heads
    • -
    • androids limbs now show up as intended
    • -
    • emps now work from 1-100 severity instead of 1/2 and the severity reduces as you move from the epicentre
    • -
    -

    zeroisthebiggay updated:

    -
      -
    • ratvar gf is complete
    • +
    • fixed infectious zombies not being able to attack if host was pacifist
    • +
    • adds a way for species to have blacklisted quirks, the removal, and restoration of said quirks upon species changes
    • +
    • Now pacifists won't be able to use flamethrowers
    • +
    • Kinetic Accelerator now properly reloads a charge to it's chamber instead of nulling the variable forever
    • +
    • Now pacifists won't be able to use Kinetic Accelerators if a non-pacifist shoots it first
    -

    01 October 2020

    -

    BlueWildrose updated:

    -
      -
    • Slimepeople are given unique laughs and screams.
    • -
    • Adds "warbles", "chimpers", and "puffs" to the customizable speech verbs for character.
    • -
    - -

    29 September 2020

    -

    timothyteakettle updated:

    -
      -
    • fixed a typo causing your right eye colour to save as the left eye colour
    • -
    - -

    28 September 2020

    -

    ArchieBeepBoop updated:

    -
      -
    • Craftable Micro Powered Fans
    • -
    -

    Degirin2120 updated:

    -
      -
    • Added engineering hazard jumpsuits, can be found in the engidrobe, comes in 3 varieties.
    • -
    -

    Putnam3145 updated:

    -
      -
    • Added a brute-force check-every-single-tile step to SSair when it has enough time to run.
    • -
    • G fuid production is now much lower.
    • -
    • Supermatter sabotage objective's gone.
    • -
    • Subterfuge objectives are now all equally likely.
    • -
    • Replaced a "(hopefully) 1" with a "2"
    • -
    • Cryoing no longer unwins already-won objectives.
    • -
    -

    SiliconMain updated:

    -
      -
    • Minor adjustment to material cost of long range atmos analyzer
    • -
    -

    Trilbyspaceclone updated:

    -
      -
    • Most drinks now have some animation in them, from basic soda bubbles fizzing around to ice cubes bobbing just a bit.
    • -
    -

    Tupinambis updated:

    -
      -
    • Ghost poly's color value is now a hex value instead of an oct value. This has been a thing for OVER FIVE YEARS
    • -
    • Updates TEG, Antimatter, Jetpack sprites (CO2 and Oxy from Eris).
    • -
    • Replaced old chair sprites with new ones ported and modified from eris.
    • -
    • Beds can now be placed both right and left.
    • -
    • Subtle changes to stool legs to give them more of a shine.
    • -
    -

    raspy-on-osu updated:

    -
      -
    • TEG power generation
    • -
    -

    thakyZ updated:

    -
      -
    • Added the ability to print the Light Replacer at the Engineering Protolathe
    • -
    -

    timothyteakettle updated:

    -
      -
    • turrets can once again be broken
    • -
    • you can now have heterochromia and select individual eye colours
    • -
    - -

    27 September 2020

    -

    SiliconMain updated:

    -
      -
    • Holograms made from projectors (atmos, engi, sec, medical, ect...) can no longer be contaminated by radiation
    • -
    - -

    26 September 2020

    -

    CoreFlare updated:

    -
      -
    • IPC's can have hair. Why wasn't this added earlier. Use the bald hairstyle for no hair.
    • -
    - -

    25 September 2020

    -

    Putnam3145 updated:

    -
      -
    • Removed a non-working proc that already had its functionality implemented in another proc in the same file.
    • -
    - -

    24 September 2020

    -

    Putnam3145 updated:

    -
      -
    • Atmos is free.
    • -
    - -

    22 September 2020

    -

    Arturlang updated:

    -
      -
    • TGUI Statpanel
    • -
    -

    YakumoChen updated:

    -
      -
    • Mechsuits, robotics jumpsuits added to RoboDrobe
    • -
    -

    timothyteakettle updated:

    -
      -
    • character previews should be more consistent now
    • -
    - -

    20 September 2020

    -

    DeltaFire15 updated:

    -
      -
    • Sutures work on simplemobs again.
    • -
    • Attacking dismembered bodyparts now targets the chest instead, for weapons aswell as unarmed attacks.
    • -
    -

    MrJWhit updated:

    -
      -
    • New sprites for chess pieces! You can craft them in-game with metal sheets.
    • -
    -

    silicons updated:

    -
      -
    • hulks can smash again (walls no longer break their hands)
    • -
    • acid no longer degrades armor
    • -
    - -

    17 September 2020

    -

    DeltaFire15 updated:

    -
      -
    • Failing the plushmium reaction can now create peculiar plushies, depending on reaction volume.
    • -
    • The mood-buff from petting a plushie now works properly again.
    • -
    • Fixed wacky necropolis loot chest behavior
    • -
    -

    EmeraldSundisk updated:

    -
      -
    • Adds the Research Director's office to Omega Station
    • -
    • Adds 2 new solar arrays (and control rooms)
    • -
    • Adds some action figures that weren't there previously
    • -
    • The CMO's office now has a light switch
    • -
    • Slight readjustments to impacted areas
    • -
    • Readjusts the toxins air supply line to (ideally) be easier to service
    • -
    • Department camera consoles should now be able to actually check appropriate cameras
    • -
    • Xenobiology can now be locked down (by the Research Director)
    • -
    -

    MrJWhit updated:

    -
      -
    • Adds a brain damage line
    • -
    -

    Putnam3145 updated:

    -
      -
    • Your balls finally feel full, again.
    • -
    -

    timothyteakettle updated:

    -
      -
    • due to changes in policy, and several lawsuits, Nanotrasen has been forced to allow disabled people to sign up
    • -
    - -

    16 September 2020

    -

    timothyteakettle updated:

    -
      -
    • fixed an icon path
    • -
    - -

    12 September 2020

    -

    01 October 2020

    -

    BlueWildrose updated:

    -
      -
    • Slimepeople are given unique laughs and screams.
    • -
    • Adds "warbles", "chimpers", and "puffs" to the customizable speech verbs for character.
    • -
    - -

    30 August 2020

    -

    raspy-on-osu updated:

    -
      -
    • new explosion echoes
    • -
    • explosion echo range
    • -
    • 5 new explosion related sounds
    • -
    - -

    28 August 2020

    -

    EmeraldSundisk updated:

    -
      -
    • Adds more paper to the library
    • -
    • The law office now has a desk window
    • -
    • Expands most of CogStation's exterior airlocks. Slightly adjusts surrounding areas to accommodate this.
    • -
    • Updates some of CogStation's paperwork
    • -
    • The rat in the morgue turned themselves into a possum. Funniest shit I've ever seen.
    • -
    • Adjusts some area designations so cameras should receive power properly
    • -
    • Cleans up an errant decal
    • -
    -

    Hatterhat updated:

    -
      -
    • Traitor holoparasites can now only be bought once, because apparently you can only have one active holopara.
    • -
    • PDA bombs can now only be bought once per uplink.
    • -
    -

    lolman360 updated:

    -
      -
    • atmos = radiation = chemistry.
    • -
    -

    shellspeed1 updated:

    -
      -
    • Adds slow mode for iv drips
    • -
    -

    timothyteakettle updated:

    -
      -
    • an ancient game over a thousand years old has re-emerged among crewmembers - rock paper scissors
    • -
    • customization features appear in alphabetical order where necessary
    • -
    • bokken do two more stamina damage now
    • -
    • you can now choose a body sprite as an anthromorph or anthromorphic insect, and can choose from aquatic/avian and apid respectively (and obviously back to the defaults too)
    • -
    - -

    27 August 2020

    -

    silicons updated:

    -
      -
    • eyebeam lighting can only have 128 maximum HSV saturation now.
    • -
    • no more shotgun stripper clips in boxes.
    • -
    • goliath tentacles now do 20 damage to mechs at 25% ap
    • -
    -

    timothyteakettle updated:

    -
      -
    • changing your character's gender won't randomize its hairstyle and facial hairstyle now
    • -
    - -

    26 August 2020

    -

    ancientpower updated:

    -
      -
    • Ghosts can read newscasters by clicking on them.
    • -
    -

    silicons updated:

    -
      -
    • hierophant vortex blasts now have 50% armor penetration vs mecha
    • -
    • ventcrawling now kicks off every attached/buckled mob, even for non humans.
    • -
    - -

    25 August 2020

    -

    Hatterhat updated:

    -
      -
    • Insidious combat gloves have been replaced by insidious guerilla gloves. They're generally the same, except now you can tackle with them.
    • -
    -

    Literallynotpickles updated:

    -
      -
    • You can now equip handheld crew monitors on all medical-related winter coats.
    • -
    -

    Putnam3145 updated:

    -
      -
    • vore now ejects occupants on death
    • -
    -

    raspy-on-osu updated:

    -
      -
    • Thermoelectric Generator power output
    • -
    -

    timothyteakettle updated:

    -
      -
    • I.P.Cs now short their circuits when expressing emotion, causing sparks to appear around them.
    • -
    - -

    24 August 2020

    -

    MrJWhit updated:

    -
      -
    • Fixes areas on expanded airlocks
    • -
    -

    silicons updated:

    -
      -
    • wormhole jaunters work
    • -
    • wormhole jaunters no longer get interference from bags of holding
    • -
    • airlocks now only shock on pulse/wirecutters instead of on tgui panel open.
    • -
    -

    timothyteakettle updated:

    -
      -
    • three new items are in the loadout for all donators
    • -
    -

    zeroisthebiggay updated:

    -
      -
    • contraband black evening gloves in kinkvend
    • -
    - -

    23 August 2020

    -

    DeltaFire15 updated:

    -
      -
    • silicons and clockies can now access APCs properly
    • -
    -

    EmeraldSundisk updated:

    -
      -
    • Medbay now has a smartfridge for organ storage
    • -
    • Slight enhancements to the station's electrical wiring layout
    • -
    • Very small library renovation
    • -
    • Exterior airlocks have been given proper air systems for safety's sake
    • -
    -

    Ghommie updated:

    -
      -
    • Stops shielded hardsuits from slowly turning the wearer into a big glowing ball of stacked energy shield overlays.
    • -
    • the shielding overlay is merely visual as result. Aim your clicks.
    • -
    -

    Ludox235 updated:

    -
      -
    • no more 10 pop xenos (25pop now)
    • -
    -

    MrJWhit updated:

    -
      -
    • Increases the majority of airlocks by 1 tile.
    • -
    • Minor adjustments to the TEG engine.
    • -
    -

    Putnam3145 updated:

    -
      -
    • Simplemobs no longer count in dynamic.
    • -
    • "Story" storyteller no longer starts at a ludicrously low threat, always.
    • -
    • Blob threat now scales with coverage.
    • -
    • One person with their pref on no longer overpowers 40 people who might not even know there is one.
    • -
    • Negative-weight rulesets are no longer put into the list.
    • -
    -

    kiwedespars updated:

    -
      -
    • removed durathread from armwraps recipe.
    • -
    -

    lolman360 updated:

    -
      -
    • breath mask balaclava
    • -
    -

    timothyteakettle updated:

    -
      -
    • lizards are now a recommended species for mam snouts
    • -
    -

    zeroisthebiggay updated:

    -
      -
    • new sprites for the temporal katana
    • -
    • suiciding with the temporal katana omae wa mou shinderius you into the shadow realm
    • -
    • twilight isnt earrape
    • -
    - -

    22 August 2020

    -

    Time-Green (copypasta'd by lolman360) updated:

    -
      -
    • plumbing
    • -
    • automatic hydro trays
    • -
    - -

    21 August 2020

    +

    02 March 2021

    LetterN updated:

      -
    • Updates and adds some of the tips
    • +
    • colorpainter: let's not dispense null
    -

    Putnam3145 updated:

    +

    SandPoot updated:

      -
    • added reftracking as a compile flag
    • +
    • Changelings will actually become the person they want to be when using "human form" ability(after having used last resort).
    + +

    01 March 2021

    SmArtKar updated:

      -
    • RSD limitation is now 500 tiles
    • -
    • Fixed broken RSD sprites
    • -
    • Removed that shuttle limit
    • +
    • Fixes decks breaking your screen
    • +
    • Fixes binders not saving cards
    • +
    • Fixes binders not saving multiple cards of the same type
    -

    timothyteakettle updated:

    +

    Vynzill updated:

      -
    • two snouts can once again be chosen in customization
    • -
    • lizard snouts work again
    • +
    • cursed rice hat right in front of the jungle gateway's entrance is now removed from this dimensional plane
    -

    20 August 2020

    -

    DeltaFire15 updated:

    +

    28 February 2021

    +

    Putnam3145 updated:

      -
    • The cooking oil damage formula is no longer scuffed.
    • -
    • Changed the clockie help-link to lead to our own wiki.
    • +
    • Polychromic windbreaker's alt-click message is now coherent
    • +
    • Toggleable suits now have an on_toggle proc to be overridden.
    -

    Fikou updated:

    +

    R3dtail updated:

      -
    • admins can now do html in ahelps properly
    • +
    • doubled max belly name length and quadrupled belly description length
    -

    Hatterhat updated:

    +

    SandPoot updated:

      -
    • Pirate threats are now announced as "business propositions", and their arrivals are now also announced properly.
    • +
    • Body rejuvenation surgery will loop until the patient is completely healed.
    -

    tiramisuapimancer updated:

    +

    dzahlus updated:

      -
    • Ethereal hair is now their body color instead of accidentally white
    • +
    • fixes toxinlovers dying from heretic stuff that should heal them instead
    -

    18 August 2020

    -

    DeltaFire15 updated:

    -
      -
    • kindle cast time: 15ds -> 25ds
    • -
    • Moved the Belligerent Scripture to where it should be in the code
    • -
    -

    Detective-Google updated:

    -
      -
    • glass floors
    • -
    • uncrowbarrable plasma floors tweak:disco inferno's plasma floors can no longer be crowbarred.
    • -
    • ghost cafe has funky fresh art
    • -
    • you can actually remove glass floors now
    • -
    • get_equipped_items is hopefully less gross
    • -
    • plasma cutters are no longer gay
    • -
    +

    27 February 2021

    Hatterhat updated:

      -
    • Slaughter demons (and laughter demons, being a subtype) are MOB_SIZE_LARGE, with one of the more immediate effects being able to mark them with a crusher and backstab them.
    • -
    • The funny blyat men have stumbled upon another surplus of Mosin-Nagants and are starting to pack them into crates again.
    • -
    • Vehicle riders can now, by default, get shot in the face and/or chest.
    • -
    • Adminspawn only .357 DumDum rounds! Because sometimes the other guy just really needs to hurt.
    • -
    • Bluespace beakers now have a chemical window through the side that shows chemical overlays.
    • -
    • Plant DNA manipulators now let you chuck things over them. Or they WOULD, if LETPASSTHROW worked half a damn.
    • +
    • Lingfists (trait_mauler) now deal no stam damage and lost their 15(!!!) armor penetration.
    -

    LetterN updated:

    +

    Putnam3145 updated:

      -
    • uplink implant states
    • -
    • tweaks how role assigning works
    • -
    -

    MrJWhit updated:

    -
      -
    • Gives ashwalkers nightvision
    • -
    • Makes tesla blast people, not the environment, to save the server.
    • +
    • Tablets now protect their contents from rads.

    TheObserver-sys updated:

      -
    • moves Garlic sprites from growing.dmi to growing_vegetable.dmi
    • -
    • Removes the unused Electric Lime mutation, it just takes up space with no actual function nor sprites.
    • -
    • Gives Catnip growing sprites
    • -
    • Removes redundant images in growing.dmi
    • +
    • Chems that should have been usable are now usable, try some cryoxadone on a plant today!!!
    • +
    +

    kappa-sama updated:

    +
      +
    • cards and card binders are now small-class items
    • +
    +

    keronshb updated:

    +
      +
    • 16 > 10 unlock cost for stronger abilities
    • +
    • Made nearly all other abilities for free.

    kiwedespars updated:

      -
    • 10 force to a fucking rubber cock.
    • -
    -

    lolman360 updated:

    -
      -
    • shotgun stripper clip nerf. ammoboxes can now accept a load_delay that happens when they attack a magazine, internal or external.
    • -
    -

    ported from tg updated:

    -
      -
    • bronze airlocks and windows can now be built
    • -
    • i also tweaked bronze flooring to be cheaper.
    • +
    • reverted the pr that absolutely gutted pugilism and made it worse than base unarmed, also gives it a second long stagger
    • +
    • removed the ability to parry while horizontal, because that's dumb and makes it easy to just time the parries right.

    silicons updated:

      -
    • stamina draining projectiles without stamina for their primary damage type now has their stamina damage taken into account for shield blocking, rather than the block being done for free for that.
    • -
    -

    timothyteakettle updated:

    -
      -
    • snowflake code tidyup
    • -
    • snowflake code for mutant bodypart selection has been rewritten to be ~14x shorter
    • -
    • meat type and horns can now be selected by any species
    • +
    • chaplain arrythmic knives can no longer be abused for infinite speed.
    -

    17 August 2020

    +

    26 February 2021

    DeltaFire15 updated:

      -
    • Cogscarabs are no longer always Pogscarabs
    • -
    -

    Strazyplus updated:

    -
      -
    • Added drakeborgs
    • -
    • Added drakeplushies
    • -
    • added drakeborg sprites
    • -
    • added drakeplushie sprites
    • -
    • changed some code - added drakeplushies to backpack loadout Removed duplicate voresleeper belly sprites from engdrake & jantidrake. [CC BY-NC-SA 3.0](https://creativecommons.org/licenses/by-nc-sa/3.0/)
    • -
    • Added CC BY-NC-SA 3.0 license details to icon/mob/cyborg moved drakeborg.dmi to icon/mob/cyborg
    • +
    • All machine-frame based tool-use actions now have state-checking callbacks.
    -

    16 August 2020

    -

    kiwedespars updated:

    -
      -
    • nerfed hypereut chaplain weapon.
    • -
    • 50% rng blockchance -> 0%
    • -
    • parry made much worse because it's an actual weapon and a roundstart one at that.
    • -
    -

    zeroisthebiggay updated:

    -
      -
    • tips
    • -
    - -

    15 August 2020

    -

    LetterN updated:

    -
      -
    • missing anomaly core icons
    • -
    • wrong state. blame the tg vertion i copied
    • -
    -

    silicons updated:

    -
      -
    • the 8 rotation limit from clockwork chairs has been removed. please don't abuse this.
    • -
    • ethereals can now wear underwear
    • -
    - -

    14 August 2020

    -

    silicons updated:

    -
      -
    • abductors can buy things
    • -
    - -

    13 August 2020

    -

    LetterN updated:

    -
      -
    • Removes fermisleepers and reverts them to tg ones
    • -
    - -

    12 August 2020

    +

    25 February 2021

    DeltaFire15 updated:

      -
    • hellgun single-pack classification: goodies -> armory
    • +
    • Traitor / Ling objective amount should now be correct again.
    -

    Detective-Google updated:

    + +

    24 February 2021

    +

    SandPoot updated:

      -
    • hallway table hallway table
    • -
    -

    Hatterhat updated:

    -
      -
    • The temporal katana is now slightly more worthy of the 2 spell point cost, with a smaller, antimagic respecting timestop, less force, and no random blockchance. Society has progressed past the need for blockchance.
    • -
    -

    LetterN updated:

    -
      -
    • Mafia Component
    • -
    • Fixed missing icons and handtele
    • -
    -

    Putnam3145 updated:

    -
      -
    • a whole lot of jank regarding funny part sprite display.
    • -
    -

    Toriate updated:

    -
      -
    • Opossums have migrated into the maintenance tunnels! Seek them out at your own peril!
    • -
    -

    ancientpower updated:

    -
      -
    • Doors added to the west side of box medbay to make things a bit more manageable.
    • -
    -

    kappa-sama updated:

    -
      -
    • smuggler satchel cost 2->1
    • -
    • radio jammer cost 5->2
    • -
    • smuggler satchel uplink description now implies that persistence is disabled
    • -
    -

    lolman360 updated:

    -
      -
    • renameable necklace (accessory, attaches to suit) and ring (glove slot.)
    • -
    • custom rename is now 2048 characters? i think it's characters.
    • +
    • Regular crowbars no longer open powered airlocks.

    silicons updated:

      -
    • You can now use anything as an emoji by doing :/obj/item/path/to/item:. This works for any /atom or subtype.
    • +
    • xeno cube makes hostile xenos now, and drops a sentinel instead of a drone.
    • +
    + +

    23 February 2021

    +

    keronshb updated:

    +
      +
    • Hyperblade to uplink with poplock
    • +
    • Removes combination of two Dragon Tooth Swords while keeping it for regular eutactics.

    timothyteakettle updated:

      -
    • syndicate agents now have access to mechanical aim enhancers which allow them to aim bullets to bounce off walls
    • -
    • ricochets work properly now for the bullets that support them
    • -
    -

    zeroisthebiggay updated:

    -
      -
    • hair and some sechuds
    • -
    • ce hardsuit radproofing
    • +
    • banning panel prioritises mobs with clients now when trying to find them if they're in the game
    -

    11 August 2020

    -

    Hatterhat updated:

    -
      -
    • PDA uplinks can now steal from pens. Properly. Just make sure to have a pen in your PDA, first.
    • -
    -

    kappa-sama updated:

    -
      -
    • tracer no longer gives you full stamheals per use
    • -
    -

    zeroisthebiggay updated:

    -
      -
    • volaju two
    • -
    - -

    10 August 2020

    -

    Hatterhat updated:

    -
      -
    • Parry counterattack text now shows up.
    • -
    • Sterilized gauze is now better at stopping bleeding, and applies slightly faster. Very slightly faster.
    • -
    • Ointment and sutures now hold more in a stack (12 and 15, respectively).
    • -
    • Sterilized gauze can now be made by just pouring 10u sterilizine onto standard medical gauze, instead of having to craft it. Why you had to craft it, I will honestly never know.
    • -
    • Proto-kinetic glaives are more expensive, stagger/cooldown on failed parries increased slightly, perfect parries required for counterattack.
    • -
    • New item: Temporal Katana. 2 points for wizards, timestops upon successful parry, bokken quickparry stats (100 force on melee counter!).
    • -
    • Also you can *smirk. This has no mechanical effect, other than being smug.
    • -
    -

    KeRSedChaplain updated:

    -
      -
    • Added a guide for romerol usage
    • -
    • made infectious zombies not enter softcrit and take no stamina damage
    • -
    -

    LetterN updated:

    -
      -
    • clocktheme color
    • -
    • Ports TGUI-4
    • -
    -

    Lynxless updated:

    -
      -
    • Ports TG #51879
    • -
    -

    Owai-Seek updated:

    -
      -
    • Meatballs now spawn raw from food processors.
    • -
    +

    22 February 2021

    Putnam3145 updated:

      -
    • Ethereals
    • -
    • (Hexa)crocin
    • -
    • (Hexa)camphor
    • -
    • Tweaked wording for marking tickets IC issue.
    • -
    • Rerolling your traitor goals will ONLY give you "proper" objectives.
    • +
    • (Hexa)crocin
    • +
    • (Hexa)camphor
    • +
    • Nymphomaniac quirk
    • +
    • All climaxes and arousals are now logged, as well as genital exposure.
    -

    Seris02 updated:

    +

    SandPoot updated:

      -
    • borgs being able to select and use a module when it's too damaged
    • +
    • Cyborg tablets and it's special app for self-management.
    • +
    • In the case of a doomsday device being created outside of an AI it will delete itself.
    • +
    • Some sprites for it have been added and the borg's hud light toggles been changed to only on-off (made by yours truly)
    • +
    • A lot of borg code was changed
    • +
    • Tools no longer use istype checks and actually check for their behavior.
    -

    Sishen1542 updated:

    +

    Vynzill updated:

      -
    • gave chairs active block/parry in exchange for removal of block_chance
    • -
    • replaces box whiteship tbaton with truncheon
    • +
    • cursed rice hat that's hard to find and obtain, along with a couple other hats
    • +
    • a replacement toy gun for donksoft lmg
    • +
    • gorillas to the jungle gateway, friendly, even when attacked.
    • +
    • couple mapping errors I noticed, most importantly a missing window in the chapel.
    • +
    • shotgun and donksoft lmg removed, captain coat nerfed armor values.
    • +
    • leaper healthpool from 450 to 550 hopefully making it more of a struggle, and gives it a name.
    • +
    • leaper pit is more wider. The hidden room south is now more obvious to find
    -

    kappa-sama updated:

    +

    dzahlus updated:

      -
    • made the Dirty Magazines crate cost 4000 instead of 12000 credits
    • -
    • MODS I SPILLED MU JUICE HEJPPHRLP HELPJ JLEP HELP
    • -
    -

    silicons updated:

    -
      -
    • player made areas are no longer valid for malf hacking
    • -
    • default space levels is 4 again.
    • -
    • rats now swarm instead of stacking on one spot.
    • -
    • getting hit by an explosion will now barely hard knockdown, but will leave you somewhat winded.
    • -
    -

    timothyteakettle updated:

    -
      -
    • speech verbs copy through dna copying now
    • +
    • Added pain emote to getting wounded
    • +
    • added a new pain emote sounds
    -

    09 August 2020

    +

    21 February 2021

    Hatterhat updated:

      -
    • Proto-kinetic glaives (not crushers) can parry now.
    • +
    • Anomaly announcements and brand intelligence now always announce instead of having some ham-fisted chance of being a command report.
    • +
    +

    IronEleven updated:

    +
      +
    • Raises Space Vine Population Requirement from 10 to 20

    MrJWhit updated:

      -
    • Adds a second shutter on the top of the hop line
    • +
    • Removes an unnecessary % on the seed extractor.
    • +
    +

    timothyteakettle updated:

    +
      +
    • the query for checking mentors now gets properly deleted
    • +
    • vampires no longer burn in the chapel if they signed up as the chaplain
    • +
    + +

    20 February 2021

    +

    Adelphon updated:

    +
      +
    • polychromic pants
    • +
    • urban coat made polychromic
    • +
    +

    Chiirno updated:

    +
      +
    • Synthflesh now unhusks with 100u instead of requiring 101u.
    • +
    +

    SmArtKar updated:

    +
      +
    • Added some QoL changes to TCG
    • +
    • Fixed TCG cards not saving
    • +
    +

    TyrianTyrell updated:

    +
      +
    • fixed the signed language so that you can actually use it, and that it's unusable when it's meant to be.
    • +
    +

    timothyteakettle updated:

    +
      +
    • stops people using Message All on PDAs when their cartridge doesn't allow it
    • +
    + +

    19 February 2021

    +

    Putnam3145 updated:

    +
      +
    • Buzz Fuzz's addiction threshold is now a can and a sip as intended.
    • +
    +

    timothyteakettle updated:

    +
      +
    • staring into pierced realities is now logged
    • +
    + +

    18 February 2021

    +

    BlueWildrose updated:

    +
      +
    • Admins now receive messages regarding certain holodeck actions.
    • +
    +

    Hatterhat updated:

    +
      +
    • Free Golem Ship GPSes now start as disabled. Like they were supposed to.
    • +
    +

    LetterN updated:

    +
      +
    • No more liver damage when you opt out of "hornychems"
    • +
    +

    SmArtKar updated:

    +
      +
    • Added a new TCG card game
    • +
    +

    dzahlus updated:

    +
      +
    • Removed maroon objective due to toxic gameplay behaviour
    • +
    +

    shellspeed1 updated:

    +
      +
    • floor bots place plating before tiles now.
    • +
    • gets rid of another tile duplication issue.

    silicons updated:

      -
    • immovable rods no longer drop down chasms
    • -
    • fun removal: squeaking objects now have an 1 second cooldown between squeaks, and will have a 33% chance of interrupting any other squeaking object when Cross()ing, meaning no more ear-fuck conveyor belts.
    • +
    • priviledge --> privilege
    -

    08 August 2020

    +

    16 February 2021

    +

    silicons updated:

    +
      +
    • sprint removal entry added, UI will revert to old UI while this is active.
    • +
    + +

    15 February 2021

    +

    Adelphon updated:

    +
      +
    • polychromatic shoes
    • +
    • polychromatic windbreaker
    • +
    • polychromatic canvas cloak
    • +
    • digitigrade charismatic suit texture
    • +

    DeltaFire15 updated:

      -
    • Roundstart cultists now start with a replica fabricator - no brass though, make your own.
    • -
    • Kindle cast time: 10 > 15, mute after stun end: 2 > 5, slur after mute end: 3 > 5
    • -
    • The ratvarian spear no longer adds negative vitality under very specific circumstances.
    • -
    • The Ratvarian Spear can parry now! Short parries with low leeway, but low cooldown.
    • -
    • The brass claw, a implant-based weapon which gains combo on consecutive hits against the same target.
    • -
    • The sigil of rites, a sigil used to perform various rites with a cost of power and materials
    • -
    • The Rite of Advancement: Used to add a organ or cyberimplant to a clockie without need for surgery.
    • -
    • The Rite of Woundmending: Used to heal all wounds on another cultist, causing toxins damage in return.
    • -
    • The Rite of the Claw: Used to summon a brass claw implant. Maximum of 4 uses per round.
    • +
    • Kneecapped pugilist parries somewhat.
    • +
    • Slightly nerfed default unarmed parries.
    • +
    • Slightly nerfed traitor armwrap parries.
    • +
    • Pugilist parries now cannot perfectly defend against projectiles, as they were supposed to.
    • +
    • Some parrying numbers that one would think were in seconds didn't have the SECONDS. I added those.
    • +
    • Clock cultists now yell alot less when invoking scripture.
    -

    Hatterhat updated:

    +

    dzahlus updated:

      -
    • You can now buy a toolbox's worth of Mosin-Nagant ammo for a fairly discounted price.
    • -
    • Revolvers from the dedicated kit now have reskinning capabilities.
    • -
    • You can now actually buy the riflery primer, which lets you pump shotguns and work the Mosin's bolt faster.
    • -
    • Bulldog slug magazines now have a unique sprite.
    • -
    -

    Ludox235 updated:

    -
      -
    • Removed an abductee objective that told you to remove all oxygen.
    • -
    • Added a new abductee objective to replace the removed one.
    • -
    -

    Sishen1542 updated:

    -
      -
    • 🅱️oneless
    • -
    • squishy slime emotes
    • -
    -

    timothyteakettle updated:

    -
      -
    • heparin makes you bleed half as much now
    • -
    • cuts make you bleed 25% less now
    • -
    • more items in the loadout and loadout has subcategories now for easier searching
    • -
    - -

    07 August 2020

    -

    dapnee updated:

    -
      -
    • fixed active tufs on some space ruins, murderdome VR, and a few on pubby, changed cargo autolathe to techfab, messed with pipe room leading to monastery.
    • -
    -

    lolman360 updated:

    -
      -
    • vendors are now unanchored when tipped. it just fell over it's not bolted to the ground anymore.
    • -
    • podpeople no fat when sunbathing.
    • +
    • Added new emote
    • +
    • added a new emote sound

    silicons updated:

      -
    • explosions only recurse one level into storage before dropping 1 level per storage layer.
    • -
    • volumetric storage is now minimum 16 pixels per item because 8 was ridiculous
    • -
    • shieldbash balanace --> balance
    • -
    • attempting to send too long of an emote will now reflect it back to you instead of cutting it off and discarding the overflow.
    • -
    • holoparasites can now play music
    • -
    • lethal blood now causes damaging bleeding instead of outright gibbing
    • +
    • people on the ground hit less hard in unarmed combat. rng miss remove from punches.
    • +
    • chat highlighting no longer drops half your entered words.
    -

    06 August 2020

    -

    Auris456852 updated:

    +

    14 February 2021

    +

    DeltaFire15 updated:

      -
    • Added B.O.O.P. Remote Control cartridges to the PTech.
    • +
    • The antag panel now correctly shows the names of cultist / clockcult datum subtypes.
    • +
    • Adding clock cultists via the admin panel now works correctly.
    • +
    • Xeno larvae should now be able to ventcrawl again.

    Hatterhat updated:

      -
    • Proto-kinetic glaives! Essentially a proto-kinetic crusher with a different blade, handguard, and goliath hide grip. Expensive, but elegant.
    • -
    • Door charges no longer knock people out.
    • +
    • Repairing sensors on jumpsuits now requires a fully-intact jumpsuit. Find some cloth.
    • +
    • Durathread armor kits now require you to have a fully-repaired jumpsuit, first, with no attachments.
    • +
    • Durathread armor kits now no longer weave the entirety of the jumpsuit armor universe into having armor.
    -

    Ludox235 updated:

    +

    TyrianTyrell updated:

      -
    • You can now buy damaged AI upload modules in the traitor's uplink.
    • +
    • added a define for multilingual granted languages, and changed the multilingual trait to use it.
    -

    Seris02 updated:

    + +

    13 February 2021

    +

    Hatterhat updated:

      -
    • fixed ghost chilis
    • -
    -

    Trilbyspaceclone updated:

    -
      -
    • 4 New blends of tea have been shipped to the station, and how to make them has been leaked!
    • -
    -

    b1tt3r1n0 updated:

    -
      -
    • Added the warp implant
    • -
    -

    dapnee updated:

    -
      -
    • added a hallway to telecoms for engineers to get there on meta
    • -
    -

    kappa-sama updated:

    -
      -
    • dildo circuit assemblies
    • -
    -

    lolman360 updated:

    -
      -
    • The Tendril-Mother on Lavaland has remembered how to make ashwalkers who know how to speak Draconic again.
    • +
    • Energy bolas now take 2.5 seconds to remove and dissipate on removal.

    timothyteakettle updated:

      -
    • nanotrasen has decided to fire all disabled members of the security division and confiscate certain sentimental items from doctors
    • -
    • the custom tongue preference now passes through cloning so you spawn with your selected tongue
    • -
    • several changes to travelling traders so they look better and spawn slightly less often
    • -
    -

    zeroisthebiggay updated:

    -
      -
    • nukies can buy holoparasites
    • +
    • migration error to version 39+ of savefiles is now logged instead of messaging all online admins in the chat
    -

    04 August 2020

    -

    Seris02 updated:

    +

    12 February 2021

    +

    Hatterhat updated:

      -
    • lizard spines
    • +
    • The ATVs on SnowCabin.dmm have been replaced with snowmobiles.
    • +
    +

    MrJWhit updated:

    +
      +
    • Random deltastation fixes.
    • +
    • Gives boxstation vault door actual vault door access
    • +
    +

    silicons updated:

    +
      +
    • Voice of God - sleep removed, stun staggers instead, knockdown is faster but does not do stamina damage, vomit is faster but doesn't stun
    • +
    + +

    11 February 2021

    +

    Adelphon updated:

    +
      +
    • Charismatic Suit
    • +
    • Urban Jacket
    • +
    +

    DeltaFire15 updated:

    +
      +
    • Added nanogel to the robodrobe.
    • +
    +

    Putnam3145 updated:

    +
      +
    • Config to keep unreadied players from mode voting
    • +
    +

    dzahlus updated:

    +
      +
    • fixes grenadelaunch.ogg being used where it shouldn't and makes mech weapons use correct sound
    • +
    +

    keronshb updated:

    +
      +
    • 10 > 30 second for Warp Implant cooldown
    • +
    • Comments out power sink objective.

    timothyteakettle updated:

      -
    • due to further advancements in medical technology, you can now have holes poked into your body for fun and enjoyment
    • +
    • persistent blood should stop being invisible and alt clicking it shouldn't return the entire spritesheet
    • +
    • pickpocketing is now logged using log_combat

    zeroisthebiggay updated:

      -
    • prefs for headpat wagging
    • +
    • the aesthetic sterile mask no longer hides faces so you can cosplay egirls and keep flavortexts
    -

    03 August 2020

    +

    09 February 2021

    +

    Chiirno updated:

    +
      +
    • Adds clown waddle to clown shoes. Enhanced Clown Waddle Dampeners can be engaged in-hand with ctrl+click, _but why would you?_
    • +
    +

    MrJWhit updated:

    +
      +
    • Re-adds theater disposal outlet, and makes dorms disposal able to have things sent to it on boxstation.
    • +
    +

    TyrianTyrell updated:

    +
      +
    • made default tongue able to speak signed language.
    • +
    +

    timothyteakettle updated:

    +
      +
    • sentient viruses can now infect synths and ipcs
    • +
    + +

    07 February 2021

    +

    Thalpy updated:

    +
      +
    • Dispenser: Adds the ability to store a small amount of reagents in the machine itself for dispensing. Reacting recipies cannot be stored. Size of storage increases with bin size.
    • +
    • Dispenser: Allows reagents to be color coded by pH
    • +
    • Dispenser: Each reagent displays it's pH on hover
    • +
    • Dispenser: Allows the user to toggle between buttons and a radial dial
    • +
    • Dispenser: When the dispencer is upgraded it can dispense 5/3/2/1 volumes based on rating refactor: Dispenser: as it was before. This does not break recorded recipes.
    • +
    • Adds a round function to some numbers so they're not huge
    • +
    • The Chem master can now get purity for all reagents when analysed
    • +
    • Synthissue fixes
    • +
    • buffers now have a strong and weak variant. Weak can be dispensed, and strong can be created. Strong buffers are 6x more effective.
    • +
    • Some buffer pH edge calculation fixes
    • +
    +

    TyrianTyrell updated:

    +
      +
    • added a signed language, that can't be used over the radio but can be used if you're mute. also added the multilingual trait.
    • +
    • hopefully added an icon for the signed language.
    • +
    • changed how some traits function slightly.
    • +
    +

    dzahlus updated:

    +
      +
    • tweaked a few sounds
    • +
    • added a new weapon sounds
    • +
    • removed old weapon sounds
    • +
    • changed some sound related code
    • +
    +

    silicons updated:

    +
      +
    • syndicate ablative armwraps have been added.
    • +
    + +

    05 February 2021

    +

    SmArtKar updated:

    +
      +
    • The orbit menu now has an Auto-Observe button! No more sifting through the lame observe menu to snoop in people's backpacks! Also, orbit menu now refreshes.
    • +
    • KAs are no longer getting broken when fired by a circuit
    • +
    +

    keronshb updated:

    +
      +
    • Force and damage > 15 from 18/25
    • +
    • Knockdown put down to 5 from 30
    • +
    • Armor pen down to 10 from 100.
    • +
    • Makes cell chargers, charge faster.
    • +
    +

    raspy-on-osu updated:

    +
      +
    • alien royals can no longer ventcrawl
    • +
    +

    shellspeed1 updated:

    +
      +
    • There actually needs to be people for zombies to happen now.
    • +
    +

    timothyteakettle updated:

    +
      +
    • dwarf facial hair is no longer randomised
    • +
    + +

    03 February 2021

    +

    Hatterhat updated:

    +
      +
    • The green energy sabre's sprite now respects proper handedness.
    • +
    + +

    02 February 2021

    +

    silicons updated:

    +
      +
    • pais can now be carried around piggybacking/fireman
    • +
    • Meth and Nuka Cola once again, speed you up.
    • +
    + +

    31 January 2021

    +

    Putnam3145 updated:

    +
      +
    • fermichem explosion EMPs don't cover the entire station
    • +
    + +

    30 January 2021

    +

    timothyteakettle updated:

    +
      +
    • adds 'clucks', 'caws' and 'gekkers' to the speech verb list
    • +
    +

    zeroisthebiggay updated:

    +
      +
    • some more FUCKING hairs
    • +
    • uncodersprites the advanced extinguisher
    • +
    + +

    29 January 2021

    +

    MrJWhit updated:

    +
      +
    • Ported the QM, Captain, CMO, and HoS cloaks from beestation.
    • +
    • Removes excess air alarms from boxstation
    • +
    +

    TripleShades updated:

    +
      +
    • fixes engineering secure storage being the wrong area because I fucked that up previously my bad
    • +
    • removes funny extra light switch under right surgery table in surgery oops
    • +
    • Added chairs to the corpse launch viewing area
    • +
    • Small garden plot for flowers for parity with other station Chapels
    • +
    • Plain Bible to glass tables in Chapel
    • +
    • Candles and Matchbox to glass tables in Chapel
    • +
    • More glass tables, with a chaplain figure and another spare bible.
    • +
    • Bookcase to Box Chapel for parity with other station Chapels
    • +
    • Minimoog to Box Chapel as substitute for a church organ
    • +
    • Holy department sign just below Chapel change: Expanded the corpse launching area to feel less congested change: Added windows to the corpse launch so you can look inside I guess? change: Moved flowers and burial garments to the corner next to the corpse launcher change: Box Chaplain's office door is moved over one change: Confessional is now connected to Chaplain's office for parity with other station Chapels change: Moved coffins over to old confessional location change: Box Chapel now has pews instead of stools change: Box Chapel Confessional is now lit instead of being nearly pitch black remove: Two coffins from Chapel
    • +
    +

    timothyteakettle updated:

    +
      +
    • the miner bedsheet will now increment its progress when you redeem points from the ORM
    • +
    • you can add custom names and descriptions to item's on the loadout now
    • +
    +

    zeroisthebiggay updated:

    +
      +
    • roundstart aesthetic sterile masks and roundstart paper masks
    • +
    • more accessory slot items
    • +
    • cowbell necklace happy 2021
    • +
    • shibari ropes & torn pantyhose
    • +
    + +

    28 January 2021

    +

    silicons updated:

    +
      +
    • colormates can now paint some mobs.
    • +
    • 1 dev explosions shouldn't delete brains anymore
    • +
    + +

    27 January 2021

    +

    ArcaneMusic, ported by Hatterhat updated:

    +
      +
    • Strike a hydroponics tray with a fully-charged floral somatoray to lock in a mutation.
    • +
    • Floral somatorays now have the ability to force a mutation in a plant. This should drain the cell in a single shot, but we'll see.
    • +
    • Somatorays now take uranium to craft instead of radium.
    • +
    +

    Arturlang updated:

    +
      +
    • Actually adds a right click give option
    • +
    • Revenants can now clickdrag to throw stuff at people, with some items doing various things at the same time.
    • +
    +

    DeltaFire15 updated:

    +
      +
    • The woundmending rite no longer causes runtimes.
    • +
    • Ratvarian borgs can now use their tier-0 spells.
    • +
    • Ratvarian borgs can always use their assigned spells, if there is enough power.
    • +
    • The heretic antag panel now shows their sacrifices & current sacrifice targets.
    • +
    • The heretic roundend report now shows their sacrifices and nonsacrificed targets.
    • +
    • Living hearts can no longer select the same target as another living heart, removing a certain problem.
    • +
    +

    Hatterhat updated:

    +
      +
    • Department budget cards have been readded. TO THE CODE. NOT LOCKERS.
    • +
    • Also budget cards now look more like every other ID - see tgstation#55001.
    • +
    • One of the contractor tablet's payouts has been raised from a small payout to a medium payout.
    • +
    • The free golem ship's GPSes no longer start on. They were never meant to, but they did.
    • +
    • Headsets can't be found on most legion corpses now.
    • +
    • The flash on the assistant corpse is gone, too.
    • +
    +

    MrJWhit updated:

    +
      +
    • Remaps some air alarms for sanity.
    • +
    +

    SandPoot updated:

    +
      +
    • The drop circuit can no longer drop things that are not inside it.
    • +
    +

    raspy-on-osu updated:

    +
      +
    • bespoke ventcrawling element not detaching due to malformed call
    • +
    +

    shellspeed1 updated:

    +
      +
    • Floorbots had had a software update, preventing them from dogpiling on their target as easily as they did before.
    • +
    • Floorbots will now play a small chime when stacked on top of each other to indicate that they're moving apart.
    • +
    +

    timothyteakettle updated:

    +
      +
    • blobs can use the 'me' verb
    • +
    • adminhelps and pms only sanitize once instead of twice
    • +
    + +

    25 January 2021

    +

    MrJWhit updated:

    +
      +
    • Alien radio code
    • +
    • Microwave can now be cleaned by a damp rag as well as soap.
    • +
    • Removes some unused code, and improves some other code.
    • +
    • The AI has a verb to look up and down z-levels
    • +
    • Making a monkey into a human doesn't unanchor random things on the tile
    • +
    • Makes a few slight improvements to drinking code
    • +
    • Makes encryption keys be put in the hands of the user when able instead of being dropped on the floor when removed from headsets
    • +
    +

    raspy-on-osu updated:

    +
      +
    • ventcrawling
    • +
    +

    silicons updated:

    +
      +
    • you can now shove yourself up in any intent, not just help.
    • +
    + +

    22 January 2021

    +

    Arturlang updated:

    +
      +
    • Adds a way to give items to people, you can combat mode rightclick to offer it to one person, right click on people without mode and click the give verb, or use the hotkey CTRL G to offer it to everyone around you
    • +
    + +

    21 January 2021

    +

    Acer202 updated:

    +
      +
    • Main mining shuttle should no longer look at the public mining shuttle and attempt to dock ontop of it. Monastery shuttle should now function again.
    • +
    +

    Acer202, with minor help from The0bserver updated:

    +
      +
    • After internal deliberation, CentCom has decided to run a limited reinstatement of public mining shuttles for use in more tried and true station classes. CentCom would like to remind you that this privilege is easily revoked, and that abuse may result in immediate detonation.
    • +
    • Restores the mining shuttle on Pubby, Box, Delta, Meta, and Lambda Station.
    • +
    +

    ArcaneMusic, The0bserver-sys updated:

    +
      +
    • New from Hydrowear LLC: The Botanical Belt! This handy yellow belt lets you hold most of your botany gear, and a few beakers for reduced bag and floor clutter!
    • +
    • Gives Hydrotrays plumbing pipes automatically, allowing you to make a self sustaining tray via plumbing.
    • +
    • Gives Service access to Bluespace Beakers, at last, gives Cargo, Science, and Medical the ability to construct reinforced plungers for use on lavaland.
    • +
    +

    ArchieBeepBoop updated:

    +
      +
    • Upgraded Advanced RTG Machine Preset
    • +
    • Outlet Injector Mapping Asset Layer Fix
    • +
    • Jacqueen and the Christmas tree should no longer spawn abstract things that can cause shittons of runtimes.
    • +
    +

    Arturlang updated:

    +
      +
    • You can't tackle in nograv anymore
    • +
    • You cannot spam drink from blood bags anymore
    • +
    • Blood bag drinking inefficiency is now the right way, so you loose some of the blood drinking it straight
    • +
    • Handles more edge cases with construct soul returning
    • +
    • Being sacrificed by the cult no longer removes all hope of rescue.
    • +
    • Makes construct mind returning more robust
    • +
    • Prayers to admins now do a wee ding sound for all prayers, instead of just chaplains
    • +
    • Fixes the mint machine's UI
    • +
    • Hopefully fixes whitescreen issues for TGUI UI's by giving assets more time to get to the client
    • +
    • Fixes hijack implant APC UI, again
    • +
    • Comments out spaceman dmm do not sleeps for mob/proc/CommonClickOn, atom/proc/attack_hand, datum/proc/keyLoop and mob/living/proc/Life
    • +
    • Bloodsuckers tresspass ability can no longer work while they are not awake.
    • +
    • The cursed heart now only takes away half as much blood every loop, and can be used as long as you are alive, instead if only you are awake/able to use your hands
    • +
    +

    Bhijn updated:

    +
      +
    • Changeling loudness is now determined as an average of all their abilities, rather than the sum
    • +
    • To compensate for this, blood tests now require a loudness value of 1 or higher to detect ling blood. Additionally, blood test explosions are now triggered only when the loudness value is higher than 2.
    • +
    +

    BlackMajor updated:

    +
      +
    • Cyborg hypospray no longer injects if it means OD'ing while on help intent.
    • +
    +

    BlueWildrose updated:

    +
      +
    • Nyctophobia quirk now has some light lag compensation.
    • +
    • Fixes cloning computer UI not updating when pressing certain buttons - also adds extra check for names to update a message
    • +
    • Removes oversized genitalia analysis from medical scanners, since huge dick and titty are no longer a problem anymore thanks to advancements in that kind of technology when it comes to chemical fun times growth.
    • +
    • Fixed species-specific drinks not giving a mood boost if you are that species.
    • +
    • You will now only unbuckle fireman-carried/piggybacked people on disarm or harm intent.
    • +
    • The traitor AI can no longer activate the doomsday device while carded.
    • +
    • Fixes noodle size appearance for 12+ inch members.
    • +
    • Fixed the subtle hotkey being weird with its input prompts.
    • +
    • Adds a subtler anti-ghost hotkey. Default key is 6.
    • +
    • No more straining when your cock or breasts are growing via incubus draft or succubus milk.
    • +
    • PubbyStation now has two Christmas Tree spawners.
    • +
    • You can now have a max-roundstart-dicksize-config inch long johnson before you start suffering blood loss and slowdowns instead of a 20 inch one.
    • +
    • Color Mates have been added to all stations (except Snaxi). Enjoy coloring your attire without having to bug science!
    • +
    • Polychromic hoodies that were obtained from the loadout have functional colorable hoods now.
    • +
    • Adds in timid woman/man costumes. Available at your autodrobe! Also adds in garters as some new socks.
    • +
    • Corrected the capitalization in gasmask concealment examine text
    • +
    +

    Chiirno updated:

    +
      +
    • Added the paramedics EVA suit as a purchase from the cargo console.
    • +
    • Paramedics office and Surgery Storage Room
    • +
    • Remodeled the surgery room, as well as shrunk Morgue and Starboard Emergency Storage. Fiddled with some areas for better map edit clarity and fixed one runtime in Vacant Office A.
    • +
    • Added the paramedic closet sprite, a paramedic colored medical3 closet.
    • +
    • Added a paramedic closet, which is the standard medical3 closet with their suit, a pinpointer, and a crew monitor added.
    • +
    • Nightmare now deals additional damage to most light sources.
    • +
    • Nightmare now one-shots miners beacons and glowshrooms
    • +
    • Portable Chem Mixer now researchable from biotech node.
    • +
    • Chem masters can now dispense 20 instances of its outputs instead of 10.
    • +
    +

    Delams-The-SM updated:

    +
      +
    • Added 3 new emotes *hiss *purr *meow
    • +
    • ported sounds from Citadel RP for *purr and *meow
    • +
    • fixed randomization of colors for things like mulligan and Stabilized green slime extract for matrixed body parts
    • +
    +

    DeltaFire15 updated:

    +
      +
    • Biomechanical (hybrid) bodyparts now have access to wound-fixing surgeries.
    • +
    • A wound being fixed no longer just qdel()s surgeries connected to it.
    • +
    • Some robotic surgery steps are now a bit more clear.
    • +
    • Organs no longer get fed to people after successfully being inserted into them.
    • +
    • Not completing the do_after of a surgery no longer causes you to attack the target with whatever you were holding.
    • +
    • IPC cells & power cords are now printable after they are researched.
    • +
    • A new surgery, allowing revival of synths without a defib at hand.
    • +
    • Semi-permanent damage of Synth limbs caused by passing the damage threshold: 10 <- 15.
    • +
    • The embed removal surgery now has a version for Synths.
    • +
    • EMPs no longer hardstun Synths.
    • +
    • Portals no longer runtime because of incorrect args.
    • +
    • Abductors now can use experimental organ replacement surgery on robots / synthetics.
    • +
    • Fixes a minor incorrectness in ratvarian borg slabs (ratvar_act -> ui_act)
    • +
    • Changelings no longer double-deathgasp when activating the regen stasis ability while not dead.
    • +
    • People installing KA modkits in miner borgs is no longer broken.
    • +
    • Fixes the tail entwine messages displaying incorrectly.
    • +
    • Antagging / Deantagging Heretics now properly sets their special role.
    • +
    • The borg VTEC ability now actually gets removed when the upgrade is removed.
    • +
    • Supplypods shouldn't cause runtimes anymore, and shrapnel (pelletclouds) should work for them.
    • +
    • Robots (anyone with the robotic_organism trait) have toxins damage replaced with system corruption. See the PR for details.
    • +
    • Clockwork rites now support hiding specific rites from neutered servants.
    • +
    • AIs now only have to kill people once instead of permanently.
    • +
    • Scripture no longer sometimes eats part of its invocation.
    • +
    • APCs and silicons are now more susceptible to powerdrains (by the power_drain() proc, which is rare)
    • +
    • Void Volt has been modified from a chant to a singular pulse.
    • +
    • Robotpeople are now fully immune to the effects of alcohol (drunkness etc.)
    • +
    • Renames the alcohol intolerance trait in the code to make what it does more clear.
    • +
    • Self-fueling weldingtools recharge fuel properly again.
    • +
    • Brass welders now actually recharge faster than experimental ones.
    • +
    • Repeatable surgery steps can no longer cause an infinite loop if not completing the do_after
    • +
    • The Revenant self-revive ability is no longer broken.
    • +
    • Loot items mobs drop are no longer always failing to initialize.
    • +
    • Instant summons can no longer do wacky stuff with disposals (and nukes).
    • +
    • Objectives are no longer very broken.
    • +
    • Bloodcult stunhands now work against clockies like they were supposed to instead of hardstunning.
    • +
    • zeolites are now actual fermichems instead of being incredibly easy to make.
    • +
    • Using syringes / droppers on chem heaters with beakers in them works again.
    • +
    • Some edge cases causing issues with system corruption shouldn't be able to occur anymore.
    • +
    • Cyborg B.o.r.i.s. installation now checks for if the chest has a cell, just like how it does with MMIs.
    • +
    • The 'Your body is in a cloner' notification works again
    • +
    • Hijack implants should work properly again (or, at least better)
    • +
    • Liches are now good skeletons again instead of weak ones
    • +
    • The piratepad control cannot be destroyed again.
    • +
    • Pirates have received new supplies of jetpacks instead of useless oxygen tanks
    • +
    • Ratvarian AIs are once again able to show their linked borgs Ratvar's light
    • +
    • Hijackers are once again unable to detonate borgs without being adjacent to the console
    • +
    • Automated annoucement systems and gulag ore consoles no longer waste emag charges
    • +
    • Automated announcement systems once again can be remote controlled by non-AIs with silicon access
    • +
    • APCs being hijacked multiple times at once is no longer possible, preventing some issues
    • +
    • Recharging APCs no longer use 0.2% of the power they should be using.
    • +
    • APCs no longer always use as much power as they can for their cell, even if it is full.
    • +
    • Vampire shapeshifting should now behave as intended
    • +
    • Some synth damage stuff has been a bit rebalanced, see the PR for details.
    • +
    • Nanogel, available at medical and robotics, which fixes internal damage in sufficiently repaired robotic limbs.
    • +
    • Robotic Limbs now each have their own damage threshhold values
    • +
    • Robotic Limb damage threshholds are now seperated into threshhold itself and mindamage when passed balance; Hybrid limbs can now be injected with hypos, but not sprayed (Still not healed by chems)
    • +
    • Brain surgery has been tweaked back to allowing robotic limbs, blacklisting IPC brains instead.
    • +
    • Robot brain surgery can now be used on organic heads, if there is a IPC brain in them somehow.
    • +
    • The robot limb heal surgery can now be used even if the target's torso is not robotic, as long as they have robotic limbs
    • +
    • BODYPART_ROBOTIC / BODYPART_ORGANIC checks replaced with helper-procs whereever possible.
    • +
    • Added a BODYPART_HYBRID define for robotic bodyparts that behave organic in some regards.
    • +
    • The transmission sigil power drain works now
    • +
    • A certain lizard (totally not me) being stupid is no longer going to break regenerate_bodyparts
    • +
    • Combat mode now will not stay permanently disabled due to status effects not working as intended.
    • +
    • Attacking some certain objects no longer has no clickdelay.
    • +
    • the blacksmithing skill now works properly
    • +
    • Anvils cannot be interacted with with hammers whilst they are already being used
    • +
    • If someone has no gloves when interacting with heated ingots, they no longer ignore their effects.
    • +
    • A runtime caused by hallucinations is gone.
    • +
    • Cargo packs marked as 'no private buying' now actually register as such.
    • +
    • Fleshmend, Anatomic Panacea and bloodsucker healing now work for Synths / IPCs.
    • +
    • Medibots now ignore people they cannot help due to their biology.
    • +
    • get_damaged_bodyparts() is no longer broken.
    • +
    • Your target cryoing will no longer give you a free greentext.
    • +
    • Sleeper UI interactiveness now behaves correctly.
    • +
    +

    Detective-Google updated:

    +
      +
    • arcade carpet
    • +
    • explosions now get broadcasted to deadchat.
    • +
    • Lick radial
    • +
    • Hilbert's jukebox works
    • +
    • arcade carpets now actually work
    • +
    • the snow taxi is no longer the slow taxi
    • +
    +

    ERP mains updated:

    +
      +
    • Subtler Around Table is now a verb
    • +
    +

    EdgeLordExe, MoonFalcon updated:

    +
      +
    • Ported a bunch of heretic-related tweaks and changes from tg
    • +
    +

    EmeraldSundisk updated:

    +
      +
    • Adds a few new area designations primarily for CogStation, incorporates them into said map
    • +
    • Reorganizes some area designations for ease of use, along with renaming the central "Router" to "Routing Depot"
    • +
    • Fixes an incorrectly designated area in CogStation
    • +
    • Changes the area designations to be not varedited since the code didn't like that anymore
    • +
    • The cargo bay conveyor belts not only work with the shuttle now but go in the right direction to boot
    • +
    • Slight visual adjustments to cargo in light of this
    • +
    • The arcade's got RAD carpet now
    • +
    • Fixes the conveyor belt issues in Delta Station's cargo wing
    • +
    • Removes some of the dirt around the affected area (presumably they would have cleaned it up while working on it)
    • +
    • Adds a floor light to fix the "dark spot" cargo had
    • +
    • Adds a new "Computer Core" area designation for CogStation
    • +
    • Fixes some missing area strings
    • +
    • Replaces some firelocks with directional ones as to ensure desks/counters can still be accessed
    • +
    • The "Skelter ruin" now has stechkins as opposed to M1911s
    • +
    • Skelter's decorative bullet casings replaced to factor in the change in caliber
    • +
    • Skelter now has a combat knife and fluff note
    • +
    +

    Ghommie updated:

    +
      +
    • You can access the mime / clown mask skins radial menu once again.
    • +
    • Dice bags no longer act like cardboard boxes.
    • +
    • Abductors should be no longer mute.
    • +
    • Item action buttons should now properly show the item current overlays, most times.
    • +
    • The blackbox should now go into your hand slot when pried out, rather than tumbling on the ground everytime.
    • +
    • The Quick Equip hotkey is now usable by all living mobs (so long they have hands and equipment slots)
    • +
    +

    Ghommie, porting PRs by MMMiracles and pireamaineach, credits to BlueWildrose too. updated:

    +
      +
    • You can now draw on plasmaman helmets with a crayon to turn their frown upside-down.
    • +
    • Plasmaman helmets no longer hide your identity when worn by themselves.
    • +
    • Plasmaman helmets now have welding visors, which can't stack with their torches in the helmet and are visible.
    • +
    +

    Hatterhat updated:

    +
      +
    • Energy sabre reskin for the energy sword - access via alt-click.
    • +
    • Alt-click reskins are fixed.
    • +
    • Defibrillators and their many, many overlays were moved to another .dmi.
    • +
    • You can now change the color of an energy sword via multitool. Not deswords. Yet.
    • +
    • The Syndicate appear to be issuing new revolver variants.
    • +
    • Basic sticky technology is now a roundstart tech. Advanced sticky technology is BEPIS-locked, though. Theoretically.
    • +
    • Non-smithed katanas (including the temporal katana) can now fit in the twin sheath.
    • +
    • Cotton and durathread processing by hand now acts like grass. Stand on a pile of cotton (or durathread) and use a single bundle from it.
    • +
    • Utility uniforms now comply with the "nonproper equipment names" thing.
    • +
    • The CapDrobe now allows the captain to get his own clothes for free. Probably.
    • +
    • All captains' clothes now offer 15 woundarmor, up from the 5. Because apparently only the suit and tie and its suitskirt subtype have this wound armor, which is dumb.
    • +
    • The nature interaction shuttle with the monkeys now has tiny fans on the airlocks in, because that's apparently a feature that was missing.
    • +
    • More bags have been added to department vendors.
    • +
    • Every roundstart species (and also ash walkers) now has flesh and bone that can be wounded.
    • +
    • Recipes for sutures, regen mesh, and sterilized gauze have been adjusted to be easier, mostly.
    • +
    • Sterilized gauze is better at absorbing blood and being a splint.
    • +
    • Energy sabres now have an off inhand.
    • +
    • The bone gauntlets should be slightly less murderously punchy on the fast punches mode.
    • +
    • RPEDs now drop their lowest part tier first when quick-emptied (used inhand).
    • +
    • Improvised gauzes can now be crafted in stacks up to 10, like their maximum stacksize implies they should be capable of doing.
    • +
    • Pouring sterilizine on gauze now takes the proper 5u per sterilized gauze instead of 10u.
    • +
    • Cryogenics now screams on common again when your fuckbuddy heads out.
    • +
    • Survival daggers! A slightly more expensive survival knife that comes with a brighter flashlight. On the blade.
    • +
    • Luxury pod capsules look different from normal capsules.
    • +
    • The wastes of Lavaland and the icy caverns of Snow Taxi rumble in unison.
    • +
    • Exosuits sold on the Supply shuttle no longer leave wreckages.
    • +
    • Apparently, shrink rays were buyable again, despite a PR having been made a while ago specifically for removing shrink rays. They're gone again.
    • +
    • Changeling bone gauntlets! They punch the shit out of people really good.
    • +
    • Guerilla gloves and gorilla gloves inherit the strip modifiers of their predecessors, because apparently they had those.
    • +
    • Pugilists now always hit the targeted limb and never miss.
    • +
    • The dock-silver standard set by Box and Meta has been enforced across maps in rotation (Delta, Pubby, Lambda).
    • +
    • The Box whiteship now has its missing tiny fan back.
    • +
    • The survival dagger light on the sprite now actually turns on and off.
    • +
    • The survival dagger in the glaive kit that can also be bought by itself is now better at butchering things.
    • +
    +

    HeroWithYay updated:

    +
      +
    • Changed description of Necrotizing Fasciitis symptom.
    • +
    • Wormhole Projector and Gravity Gun now require anomaly cores to function instead of firing pins.
    • +

    KeRSedChaplain updated:

      -
    • fixed clockwork guardians being able to reflect ranged weapons
    • +
    • Resprited the brass claw
    • +
    +

    LetterN updated:

    +
      +
    • 2 more ways to get up from z1
    • +
    • tweaked the z2 garden to be less blank
    • +
    • fixed telecomms pda log
    • +
    • Coin & Holochip support for slot machine
    • +
    • Stickybans are now saved in the DB too
    • +
    • Immersive ™ audio reverbs. (also adds multiz audio)
    • +
    • Semi-hardsync from TG
    • +
    • Updates rust-g
    • +
    • Uses git CI instead of travis/appveyor now
    • +
    • Updates git and build tests.
    • +
    • minimap text
    • +
    • ports cinematic upgrades

    Linzolle updated:

      -
    • uv penlight no longer invisible
    • +
    • entertainment monitors no longer invisible
    • +
    • entertainment monitors now light up and display text when motion is detected in thunderdome
    • +
    • lizard snouts are no longer *slightly* lighter than they are supposed to be.
    -

    dapnee updated:

    +

    MrJWhit updated:

      -
    • active turfs on box and xenohive, maintenance bar APC not being stringed correctly, turned a monitor to face a direction that makes sense, changed tag of camera in gravgen being misnamed
    • +
    • Expanded space hermit base
    • +
    • Replaced engineering fuel tank with a large fuel tank
    • +
    • Changed access to sec suit storage from armory access in every map to other security access
    • +
    • Adds a space loop to every map in toxins
    • +
    • +
    • Added the ability for cargo to buy a large welding tank
    • +
    • Tweaked large tank reagent sprites to /tg/'s
    • +
    • Gives metastation toxins storage a scrubber and a vent
    • +
    • Updates suit storage info on Tip Of the Round.
    • +
    • Increased christmas event from 22th to 27th to 10th to 27th
    • +
    • Removes an opposum from the wall
    • +
    • Donut boxes show what's inside of them now
    • +
    • Updated meat icons
    • +
    • Canceling events gives more time to stop from 10 to 30
    • +
    • Fixes two chairs on one table
    • +
    • Removed the wires connecting the AI from the rest of the station on cogstation.
    • +
    • Fixes experimenter on cogstation.
    • +
    • Less pipes in the overall area in toxins on cogstation
    • +
    • Small fixes on security on boxstation
    • +
    • Updated jukebox sprite.
    • +
    • Fixes maint area in boxstation
    • +
    • Christmas starts on the 18th now
    • +
    • Adds a goose bar sign
    • +
    • Effects can no longer trigger landmines
    • +
    • Removes the screen flashing on climax.
    • +
    • Makes gas sensors fireproof.
    • +
    • A small bucket of random fixes,
    • +
    • Minor fixes to kilo
    • +
    • Porting garbage collection tweak from /tg/
    • +
    • Updates our dark gygax sprites to /tg/'s
    • +
    • Bugfix of a morph becoming an AI eye
    • +
    • Mining station oxygen locker on the cycling airlock starts out wrenched.
    • +
    • Nerf combat knife damage
    • +
    • Code improvement on ventcrawling
    • +
    +

    NT Cleaning Crews On Break updated:

    +
      +
    • Most kinds of dirt, grime, and debris are now persistent. Get to work, jannies.
    • +
    • Dirt can now be removed by tile replacements. Other cleanable decals can't, though.
    • +
    +

    Putnam3145 updated:

    +
      +
    • Replaces majority judgement with usual judgement.
    • +
    • Toilet loot spawners don't lag the server on server start with forced hard dels.
    • +
    • vore prefs save now
    • +
    • gear harness no longer magically covers up the body mechanically despite covering up nothing visually
    • +
    • Regen coma now puts into a coma even from crit or while unconscious.
    • +
    • Regen coma now properly weakens while asleep.
    • +
    • Multi-surgery unit test no longer fails at random.
    • +
    • Dwarf speech is no longer absolutely paranoid about word replacement.
    • +
    • Spontaneous brain trauma now requires minimum 5 players
    • +
    • Grab bag works as advertised.
    • +
    • Xeno threat in dynamic tripled.
    • +
    • Vote system #defines are now strings
    • +
    • Stat panel UI for ranked choice votes
    • +
    • A fallback for dynamic antag rolling that allows for it to just try between traitor, blood brothers, heretics, changeling, bloodsucker and devil until there are enough roundstart antags. This can also happen randomly anyway. Blood brothers and devil are disabled for now, but the code is there to enable them.
    • +
    • A new storyteller, "Grab Bag", that forces the above round type.
    • +
    • atmos subsystem no longer dies if there's too many gases
    • +
    • Emotes can properly be filtered for in TGUI.
    • +
    • Holofirelocks work now.
    • +
    • adminhelping no longer removes entire admin tab
    • +
    • end of round no longer removes entire admin tab
    • +
    • Fixed a runtime in every healing nanite program.
    • +
    • removed a unit test causing master to fail
    • +
    • Planetary atmos no longer does superconduction.
    • +
    • Dynamic vote no longer shows the none-storyteller.
    • +
    • You can now exit polycircuit input
    • +
    • Polycircuits now check for range
    • +
    • gear harness alt-click is now sane
    • +
    • rolldown() and toggle_jumpsuit_adjust() now no longer mix behavior-that-should-be-overridden and behavior-that-shouldn't-be-overridden in ways that make no sense.
    • +
    • Gear harness now covers nothing.
    • +
    • Chemical stuff now displays fermichem stuff properly
    • +
    • Rad collectors now get 1.25x as much energy from radiation
    • +
    • Rad collectors now put out 1.25x as much stored energy per tick
    • +
    • Above two rad collector changes give a total 56.25% power output increase
    • +
    • Zeolites now only generate 1/5 the heat when reacting and don't require a catalyst.
    • +
    +

    Ryll/Shaps updated:

    +
      +
    • Fixed an issue with player logs becoming confused when someone triggers multiple events within one second (like being attacked by two people at the same time) that would cause holes in the logs
    • +
    +

    SandPoot updated:

    +
      +
    • You can attack a pile of money on the floor with your id to put it all in quickly.
    • +
    • Changes the limb grower a lot.
    • +
    • "Limb" costs on limbgrower are actually displayed like it was meant to all along.
    • +
    • Swaps the gift static blacklist with a global list one.
    • +
    +

    SiliconMain updated:

    +
      +
    • Engi department has gas masks in loadout
    • +
    • hololocks (which haven't worked for god knows how long) commented out until auxmos is merged
    • +
    +

    Sonic121x updated:

    +
      +
    • alarm ert hardsuit sprite for naga and canine
    • +
    • adjust the naga ert hardsuit to cover the hand
    • +
    • cydonia hardsuit helmet
    • +
    • digi sprite uniform
    • +
    • digi leg suit
    • +
    +

    SpaceManiac updated:

    +
      +
    • Fixed the maphook
    • +
    +

    Thalpy updated:

    +
      +
    • fixes some bugs in jacqs code from edits to the codebase
    • +
    +

    The Grinch updated:

    +
      +
    • infinite presents from hilbert hotel
    • +
    +

    TheObserver updated:

    +
      +
    • Re-adds the rifle stock, and sets the improv shotgun to be as it was.
    • +
    • The maintenance rifle has been shelved - for now. Watch this space.
    • +
    +

    TheObserver-sys updated:

    +
      +
    • Drake? Where's the dead fairygrass sprite?
    • +
    +

    TheSpaghetti updated:

    +
      +
    • no more tumor bread double punctuation
    • +
    +

    Trilbyspaceclone updated:

    +
      +
    • Zeolites now use gold rather then uranium for catalyst
    • +
    • Zeolites are not as hard to make ph wise
    • +
    • Making Zeolites heats up the beaker less allowing for better control
    • +
    • ASP 9mm and M1911 can now have suppressers added
    • +
    • Brass welders are 50% faster at refueling
    • +
    • redoes self fueling welders in the code to be less speggie
    • +
    • the corporate unifoms can now be gotton in the clothing mate vender
    • +
    +

    TripleShades updated:

    +
      +
    • Firelock to Surgery Bay drapes change: Swapped Nanomed and Fire Alarm button locations in both Surgery Bays change: Removes the double mirror in both Surgery Bays to be a singular mirror change: Moved an intercom to not be doorstuck below Paramedical Office remove: One Surgery Observation Fire Alarm button
    • +
    • New Paramedic Office next to Genetics where the old Genetics Reception used to be change: Surgery, Surgery Observation, and Recovery Hall layout revamped drastically change: Maints below Surgery lowered by one tile to recover lost tile space from Surgery expansion
    • +
    +

    Tupinambis updated:

    +
      +
    • Arachnids (spider people) with limited night vision, flash vulnerability, and webbing.
    • +
    +

    Vynzill updated:

    +
      +
    • new gateway mission mapadd: jungleresort map
    • +
    • fixes high luminosity eyes
    • +
    +

    Xantholne updated:

    +
      +
    • Fixed new birds changing back to basic parrot when sitting
    • +
    • New parrots from the RP server, can be found in Bird Crate in Cargo
    • +
    • You can now tuck disky into bed
    • +
    • You can now make beds by applying a bed sheet to them
    • +
    • You can now tuck in pai cards into bed
    • +
    • Added bed tucking element, can be added to any held object to allow tucking into beds
    • +
    • Twin Sword Sheaths have an equipment icon and icon when worn now and make a sound when sheathed/unsheathed
    • +
    +

    Yakumo Chen updated:

    +
      +
    • Slime Jelly is no longer obtainable from slimepeople. Go ask Xenobio
    • +
    +

    YakumoChen updated:

    +
      +
    • To lower production costs, Buzz Fuzz is now manufactured with Real™️ Synthetic honey.
    • +
    +

    Zandario updated:

    +
      +
    • Added some framework for future species expansions, including clothing refitting.
    • +
    • Made majority of the relevant Species IDs and Categories pre-defined, also for easier expansion and use.
    • +
    • lum slime sprites work again
    • +
    • Slapped the Species Defines where relevant
    • +
    +

    corin9090 updated:

    +
      +
    • The chaplain's prayer beads can now be worn on your belt slot
    • +
    +

    kappa-sama updated:

    +
      +
    • super saiyan
    • +
    • ishotgun crafting recipe no longer requires plasteel and is slightly more convenient
    • +
    • ishotgun does 45 damage now instead of 40.5
    • +
    • s
    • +
    • A new spell for the wizard and his martial apprentices, the Inner Mantra technique. It makes you punch people really good and makes you durable, but drains your energy while it's active.
    • +
    • A self-buffing spell for valiant bubblegum slayers that is ultimately useless on lavaland and probably overpowered for miner antagonists. Go figure. At least all it does is let you punch hard while draining your health every second.
    • +
    • bubblegum now drops a book that makes you into an abusive father instead of a shotgun that plays like pre-nerf shotguns
    • +
    • a powerup and powerdown sound effect
    • +
    • two icons for two buff spells
    • +
    +

    keronshb updated:

    +
      +
    • Allows Energy Bola to be caught
    • +
    • This also allows them to be dropped/picked up.
    • +
    • Adds a reduced stamina buffer for SCarp users
    • +
    • Gives SCarp users a better parry
    • +
    • Adds the SCarp bundle which includes a bo staff
    • +
    • Lets Carp costumes carry Bo Staffs
    • +
    • reduces the stamina damage of scarp slightly
    • +
    • reduced the blockchance of the bo staff
    • +
    • Adds more room to northwest maint
    • +
    • Adds a bridge between Atmos and the Turbine.
    • +
    • Blob Resource Tower to 2 points per instead of 1 point per.
    • +
    • Blob Factory Towers can be placed 5 tiles apart instead of 7.
    • +
    • Fixes Blobbernaut Factories consuming Factories if no naut is chosen.
    • +
    • Fixes Reflective Blobs
    • +
    • Re-adds the Clown Car to the clown uplink
    • +
    • 15 >16 TC cost
    • +
    • bonks on external airlocks
    • +
    • Fixes the parry data for scarp
    • +
    +

    kittycat2002 updated:

    +
      +
    • set the name of /datum/reagent/consumable/ethanol/species_drink to Species Drink
    • +
    +

    kiwedespars updated:

    +
      +
    • balanced bone gauntlets.
    • +
    • the robust dildo weapon now has sound.
    • +
    +

    necromanceranne updated:

    +
      +
    • Fixes various sprites for bokken, as well as being unable to craft certain parts and duplicate entries.
    • +
    • Bokken now come in two lengths; full and wakizashi, and two varieties: wood and ironwood. They have different stats for all four.
    • +
    • Bokken require menu crafting and part construction, as well as more complicated materials.
    • +
    • Bokken (long and short) require wood, cloth and leather to craft with a hatchet and screwdriver.
    • +
    • Ironwood bokken (long and short) require ironcap logs, cloth and leather to craft with a hatchet, screwdriver and welder.
    • +
    • Twin sheathes can only fit a pair of blades (longsword + shortsword) or they can fit two shortswords.
    • +
    • Fixed a twin sheath runtime.
    • +
    • A lot of bokken related sprites received an overhaul. Added overlay sprites for weapons sheathed in the twin sheathes.
    • +
    • The extradimensional blade received improved sprites for inhands/back sprites.
    • +
    • You can now make all the variants of the bokken.
    • +
    • Removes a duplicate sprite.
    • +
    • Renames all instances of 'ironwood' to 'steelwood'.
    • +
    • Adds new roboticist labcoat sprites!
    • +
    +

    qwertyquerty updated:

    +
      +
    • Flash the screen on climax
    • +
    +

    raspy-on-osu updated:

    +
      +
    • salicylic acid
    • +
    • space heater heating range and power
    • +
    • windoor open length
    • +
    +

    shellspeed1 updated:

    +
      +
    • Wings from Cit RP have been ported over
    • +
    • Moth wings from cit have been ported over
    • +
    • Cleaned up some pixels on existing moth wings.
    • +
    • Organized the lists for wings by if they are for moths or not and than by alphabetical.
    • +
    • Lings now have infinite space for DNA.
    • +
    • All xenomorph types have been added as corpses for mapping purposes
    • +
    • The dead xenomorphs in the lavaland xenomorph hive now have more variety.
    • +
    • Floor bots are now buildable with all toolboxes.
    • +
    • Xenomorph hybrids can now select wings ~~add: Xenomorph hybrids can now speak xenomorph~~
    • +
    • Xenomorph tongues are available for customization.
    • +
    • Mining borgs can claim points again
    • +
    • Construction bags have been added, use them to carry all sorts of construction bits.
    • +
    • A recipe has been added to cloth stacks to make material and construction bags.
    • +
    • Material bags and construction bags are now available in engineering lockers.
    • +
    • Adds the disposable sentry gun from tg for 11tc each.
    • +
    • The exofab can now print prosthetic limbs
    • +
    • The exofab was missing access to multiple cybernetic organs. This has now been rectified.
    • +
    • A new recipe for a spicy has been given to us by a strange business man.
    • +
    • The bluespace navigation gigabeacon design has been added to shuttle research for those wanting to take their ships around space more.
    • +
    • Xenomorph powers now list plasma cost in their description.

    silicons updated:

      -
    • shoves have been buffed to apply a status effect rather than a 0.85 movespeed modifier, meaning repeatedly shoving someone now renews the debuff
    • -
    • shoves now stagger for 3.5 seconds.
    • -
    • war operatives now actually time 20 minutes since roundstart to depart instead of 15.
    • -
    • explosive stand bombs can now be examined from any distance
    • -
    • explosive stand bombs are now a component.
    • +
    • nanite resistances tweaked
    • +
    • new nanite programs added for locking the user out from being modified by consoles or antivirals.
    • +
    • anomalies no longer spawn in walls
    • +
    • Twitch Plays: Clown Car
    • +
    • pugilists can now parry
    • +
    • c4 can no longer gib mobs
    • +
    • medium screens are better now
    • +
    • text formatting now uses one character instead of two around the text to emphasize.
    • +
    • colormates
    • +
    • shoving yourself up now costs 50% more
    • +
    • dullahans enabled
    • +
    • tailed individuals can now target groin to intertwine tails on grab intent.
    • +
    • Clowns now have unpredictable effects on supermatter crystals when dusting from contact.
    • +
    • anyone new to the server is lucky enough to have their sprint default to toggle instead of hold
    • +
    • stamina crit is only removed when at or under 100 stamina, rather than 140. stamina crit threshold is still at 140.
    • +
    • luxury shuttle no longer has noteleport
    • +
    • now only poly gets a headset on spawn, not all birds.
    • +
    • the warp implant now actually warps you back 10 seconds. leaves a trail, though. now unlimited us.
    • +
    • things in DEATHCOMA do not deathgasp on death
    • +
    • Meth and changeling adrenals no longer ignore all slowdowns, rather damage slowdowns.
    • +
    • you can now be an angel using a magic mirror again
    • +
    • command headsets are 120% instead of 160%
    • +
    • no more emote italics
    • +
    • players can now respawn/return to lobby as a ghost after a 15 minute (default) delay and rejoin on another character with some/many restrictions
    • +
    • cryo now preserves everything
    • +
    • Magrifle ammo no longer glows.
    • +
    • temperature slowdown divisor nerfed to 35 from 20.
    • +
    • dna melt drops all items being destroying you
    • +
    • keybinds generate anti-collision bindings where necessary automatically now
    • +
    • changeling combat mutations rebalanced. most of them take chemicals to upkeep now.
    • +
    • set-pose has been added
    • +
    • temporary flavor text renamed to set pose, fully visible in examine
    • +
    • ninja gloves no longer hardstun
    • +
    • ninja gloves now cost half as much to use to compensate
    • +
    • simple mobs are now immune to radioactive contamination
    • +
    +

    timothyteakettle updated:

    +
      +
    • time for memory loss message to show up when being revived is now correctly 300 seconds, instead of 30
    • +
    • the load away mission verb won't crash the server now
    • +
    • roundstart slimes can turn into puddles now
    • +
    • all gas masks (but welding + glass) can be alt clicked to show/hide identity
    • +
    • autosurgeons from travelling trader rewards now only have one use
    • +
    • fixes held items proccing crossed when passing someone
    • +
    • you can now get a family heirlooms based off your species instead of job
    • +
    • changeling stings retract upon turning into a slime puddle
    • +
    • you cannot transform into a slime puddle with a no drop item in your hands
    • +
    • slime puddles are now transparent and their colour looks more natural in comparison to the user
    • +
    • slime puddles are now even slower
    • +
    • slime puddles now get no protection from worn clothing
    • +
    • removes two debug messages left in from my prior eye customization pr
    • +
    • adds unlockable loadout items, corresponding category in loadouts, etc
    • +
    • added in-game age verification as an alternative to access requests
    • +
    • disabling adminhelp noises no longer disables looc
    • +
    • apids render now
    • +
    • you can now only entwine tails with people who have a tail
    • +
    • custom eyes and tongues now properly carry across cloning
    • +
    • re-adds the holoform verb for people who want to use it over going through the char list
    • +
    • eye sprites should look normal once more
    • +
    • licking people washes pie off their face
    • +
    • you can now pick your eye sprites from customization
    • +
    • looking at loadout equips loadout items on your preview image instead of job items
    • +
    • custom holoforms are now accessible through an action instead of through verbs
    • +
    • AI holoforms can now emote
    • +
    • cloning now correctly copies your blood colour, body sprite type and eye type
    • +
    • species with NOTRANSSTING cannot have envy's knife used on them
    • +
    • avian/digitigrade legs have been added for slimes
    • +
    • you can teleport bread
    • +
    • slime puddles are no longer layered down one layer
    • +
    • you cannot tackle with two paralysed arms
    • +
    • tackling with a single paralysed arm lowers your tackle roll by 2
    • +
    • circuits get pin data proc is sanitized when text is returned as data
    • +
    • loadout now has save slot support and colour choosing/saving for polychromic items
    • +
    • polychromic maid outfit
    • +
    • you can rebind communication hotkeys and they're the default now
    • +
    • you can now customize your size from 90% to 130%, going below 100% makes you have 10 less max health
    • +
    • *squeak
    • +
    • anthromorphic synth species
    • +
    • improvements to the automatic age gate
    • +
    • antag items are now of critical importance and wont fail to be placed on the character
    • +
    • a tonne of fixes to colourisation of parts, too many to name, including some sprite fixes
    • +
    • things now have their own individual primary/(secondary)/(tertiary) colours as required, and these can be modified by you
    • +
    +

    uomo91 updated:

    +
      +
    • Fixed "Show All" tab in player panel logs being broken.
    • +
    • Whispers, OOC, and various other things display differently in logs, visually distinguishing them from say logs.
    • +
    • Player panel logs will now show all logs chronologically, so you'll see commingled say and attack logs if you're on the "Show All" tab, etc...
    • +
    +

    yorii updated:

    +
      +
    • fixed botany rounding error that caused grass and other plants to misbehave
    • +
    +

    zeroisthebiggay updated:

    +
      +
    • legion now drops chests
    • +
    • Traitor assistants can now purchase the patented POGBox! Put TC into it for even higher damage!
    • +
    • MEGAFAUNA DROPS ARE LAVAPROOF
    • +
    • cool codex cicatrix inhands
    • +
    • gravitokinetic stands from tg
    • +
    • buffs stands overall
    • +
    • protector stands no longer become tposing invisible apes sometimes
    • +
    • jacqueline spawns on boxstation
    • +
    • secsheath for your cool stunsword at your local security vendor. you gotta hack it first though.
    • +
    • fuck the r*d cr*ss
    • +
    • The legion megafauna has been reworked. The fight should now be both slightly harder and faster.
    • +
    • You can no longer cheese the colossus by being a sand golem and simply being immune.
    diff --git a/html/changelogs/.all_changelog.yml b/html/changelogs/.all_changelog.yml index 99ffc455c3..ec88f1b012 100644 --- a/html/changelogs/.all_changelog.yml +++ b/html/changelogs/.all_changelog.yml @@ -26243,7 +26243,7 @@ DO NOT EDIT THIS FILE BY HAND! AUTOMATICALLY GENERATED BY ss13_genchangelog.py. Ghommie: - bugfix: You can now actually gain wiring experience from using cable coils. - bugfix: Opening the View Skill Panel shouldn't trigger messages about insufficient - admin priviledges anymore. + admin privileges anymore. Yakumo Chen, kappa-sama: - rscdel: Removes improvised handguns - rscdel: removed handsaws, improvised gun barrels (you can use atmos pipes again) @@ -27524,3 +27524,1211 @@ DO NOT EDIT THIS FILE BY HAND! AUTOMATICALLY GENERATED BY ss13_genchangelog.py. - bugfix: limb id entry in mutant bodyparts now supports switching to/from species with gendered body parts - tweak: the minimum brightness of mutant parts is now a define +2021-01-21: + Acer202: + - bugfix: Main mining shuttle should no longer look at the public mining shuttle + and attempt to dock ontop of it. Monastery shuttle should now function again. + Acer202, with minor help from The0bserver: + - rscadd: After internal deliberation, CentCom has decided to run a limited reinstatement + of public mining shuttles for use in more tried and true station classes. CentCom + would like to remind you that this privilege is easily revoked, and that abuse + may result in immediate detonation. + - rscadd: Restores the mining shuttle on Pubby, Box, Delta, Meta, and Lambda Station. + ArcaneMusic, The0bserver-sys: + - rscadd: 'New from Hydrowear LLC: The Botanical Belt! This handy yellow belt lets + you hold most of your botany gear, and a few beakers for reduced bag and floor + clutter!' + - tweak: Gives Hydrotrays plumbing pipes automatically, allowing you to make a self + sustaining tray via plumbing. + - tweak: Gives Service access to Bluespace Beakers, at last, gives Cargo, Science, + and Medical the ability to construct reinforced plungers for use on lavaland. + ArchieBeepBoop: + - rscadd: Upgraded Advanced RTG Machine Preset + - bugfix: Outlet Injector Mapping Asset Layer Fix + - bugfix: Jacqueen and the Christmas tree should no longer spawn abstract things + that can cause shittons of runtimes. + Arturlang: + - bugfix: You can't tackle in nograv anymore + - tweak: You cannot spam drink from blood bags anymore + - bugfix: Blood bag drinking inefficiency is now the right way, so you loose some + of the blood drinking it straight + - bugfix: Handles more edge cases with construct soul returning + - tweak: Being sacrificed by the cult no longer removes all hope of rescue. + - bugfix: Makes construct mind returning more robust + - tweak: Prayers to admins now do a wee ding sound for all prayers, instead of just + chaplains + - bugfix: Fixes the mint machine's UI + - bugfix: Hopefully fixes whitescreen issues for TGUI UI's by giving assets more + time to get to the client + - bugfix: Fixes hijack implant APC UI, again + - code_imp: Comments out spaceman dmm do not sleeps for mob/proc/CommonClickOn, + atom/proc/attack_hand, datum/proc/keyLoop and mob/living/proc/Life + - bugfix: Bloodsuckers tresspass ability can no longer work while they are not awake. + - tweak: The cursed heart now only takes away half as much blood every loop, and + can be used as long as you are alive, instead if only you are awake/able to + use your hands + Bhijn: + - tweak: Changeling loudness is now determined as an average of all their abilities, + rather than the sum + - tweak: To compensate for this, blood tests now require a loudness value of 1 or + higher to detect ling blood. Additionally, blood test explosions are now triggered + only when the loudness value is higher than 2. + BlackMajor: + - tweak: Cyborg hypospray no longer injects if it means OD'ing while on help intent. + BlueWildrose: + - tweak: Nyctophobia quirk now has some light lag compensation. + - bugfix: Fixes cloning computer UI not updating when pressing certain buttons - + also adds extra check for names to update a message + - rscdel: Removes oversized genitalia analysis from medical scanners, since huge + dick and titty are no longer a problem anymore thanks to advancements in that + kind of technology when it comes to chemical fun times growth. + - bugfix: Fixed species-specific drinks not giving a mood boost if you are that + species. + - tweak: You will now only unbuckle fireman-carried/piggybacked people on disarm + or harm intent. + - balance: The traitor AI can no longer activate the doomsday device while carded. + - bugfix: Fixes noodle size appearance for 12+ inch members. + - bugfix: Fixed the subtle hotkey being weird with its input prompts. + - rscadd: Adds a subtler anti-ghost hotkey. Default key is 6. + - tweak: No more straining when your cock or breasts are growing via incubus draft + or succubus milk. + - rscadd: PubbyStation now has two Christmas Tree spawners. + - tweak: You can now have a max-roundstart-dicksize-config inch long johnson before + you start suffering blood loss and slowdowns instead of a 20 inch one. + - rscadd: Color Mates have been added to all stations (except Snaxi). Enjoy coloring + your attire without having to bug science! + - bugfix: Polychromic hoodies that were obtained from the loadout have functional + colorable hoods now. + - rscadd: Adds in timid woman/man costumes. Available at your autodrobe! Also adds + in garters as some new socks. + - spellcheck: Corrected the capitalization in gasmask concealment examine text + Chiirno: + - rscadd: Added the paramedics EVA suit as a purchase from the cargo console. + - rscadd: Paramedics office and Surgery Storage Room + - tweak: Remodeled the surgery room, as well as shrunk Morgue and Starboard Emergency + Storage. Fiddled with some areas for better map edit clarity and fixed one runtime + in Vacant Office A. + - imageadd: Added the paramedic closet sprite, a paramedic colored medical3 closet. + - code_imp: Added a paramedic closet, which is the standard medical3 closet with + their suit, a pinpointer, and a crew monitor added. + - tweak: Nightmare now deals additional damage to most light sources. + - bugfix: Nightmare now one-shots miners beacons and glowshrooms + - bugfix: Portable Chem Mixer now researchable from biotech node. + - tweak: Chem masters can now dispense 20 instances of its outputs instead of 10. + Delams-The-SM: + - rscadd: Added 3 new emotes *hiss *purr *meow + - soundadd: ported sounds from Citadel RP for *purr and *meow + - bugfix: fixed randomization of colors for things like mulligan and Stabilized + green slime extract for matrixed body parts + DeltaFire15: + - balance: Biomechanical (hybrid) bodyparts now have access to wound-fixing surgeries. + - tweak: A wound being fixed no longer just qdel()s surgeries connected to it. + - tweak: Some robotic surgery steps are now a bit more clear. + - bugfix: Organs no longer get fed to people after successfully being inserted into + them. + - tweak: Not completing the do_after of a surgery no longer causes you to attack + the target with whatever you were holding. + - rscadd: IPC cells & power cords are now printable after they are researched. + - rscadd: A new surgery, allowing revival of synths without a defib at hand. + - balance: 'Semi-permanent damage of Synth limbs caused by passing the damage threshold: + 10 <- 15.' + - tweak: The embed removal surgery now has a version for Synths. + - balance: EMPs no longer hardstun Synths. + - bugfix: Portals no longer runtime because of incorrect args. + - tweak: Abductors now can use experimental organ replacement surgery on robots + / synthetics. + - bugfix: Fixes a minor incorrectness in ratvarian borg slabs (ratvar_act -> ui_act) + - bugfix: Changelings no longer double-deathgasp when activating the regen stasis + ability while not dead. + - bugfix: People installing KA modkits in miner borgs is no longer broken. + - bugfix: Fixes the tail entwine messages displaying incorrectly. + - bugfix: Antagging / Deantagging Heretics now properly sets their special role. + - bugfix: The borg VTEC ability now actually gets removed when the upgrade is removed. + - bugfix: Supplypods shouldn't cause runtimes anymore, and shrapnel (pelletclouds) + should work for them. + - rscadd: Robots (anyone with the robotic_organism trait) have toxins damage replaced + with system corruption. See the PR for details. + - code_imp: Clockwork rites now support hiding specific rites from neutered servants. + - tweak: AIs now only have to kill people once instead of permanently. + - bugfix: Scripture no longer sometimes eats part of its invocation. + - balance: APCs and silicons are now more susceptible to powerdrains (by the power_drain() + proc, which is rare) + - balance: Void Volt has been modified from a chant to a singular pulse. + - balance: Robotpeople are now fully immune to the effects of alcohol (drunkness + etc.) + - tweak: Renames the alcohol intolerance trait in the code to make what it does + more clear. + - bugfix: Self-fueling weldingtools recharge fuel properly again. + - bugfix: Brass welders now actually recharge faster than experimental ones. + - bugfix: Repeatable surgery steps can no longer cause an infinite loop if not completing + the do_after + - bugfix: The Revenant self-revive ability is no longer broken. + - bugfix: Loot items mobs drop are no longer always failing to initialize. + - tweak: Instant summons can no longer do wacky stuff with disposals (and nukes). + - bugfix: Objectives are no longer very broken. + - bugfix: Bloodcult stunhands now work against clockies like they were supposed + to instead of hardstunning. + - balance: zeolites are now actual fermichems instead of being incredibly easy to + make. + - bugfix: Using syringes / droppers on chem heaters with beakers in them works again. + - bugfix: Some edge cases causing issues with system corruption shouldn't be able + to occur anymore. + - bugfix: Cyborg B.o.r.i.s. installation now checks for if the chest has a cell, + just like how it does with MMIs. + - bugfix: The 'Your body is in a cloner' notification works again + - bugfix: Hijack implants should work properly again (or, at least better) + - bugfix: Liches are now good skeletons again instead of weak ones + - bugfix: The piratepad control cannot be destroyed again. + - bugfix: Pirates have received new supplies of jetpacks instead of useless oxygen + tanks + - bugfix: Ratvarian AIs are once again able to show their linked borgs Ratvar's + light + - bugfix: Hijackers are once again unable to detonate borgs without being adjacent + to the console + - bugfix: Automated annoucement systems and gulag ore consoles no longer waste emag + charges + - bugfix: Automated announcement systems once again can be remote controlled by + non-AIs with silicon access + - bugfix: APCs being hijacked multiple times at once is no longer possible, preventing + some issues + - bugfix: Recharging APCs no longer use 0.2% of the power they should be using. + - bugfix: APCs no longer always use as much power as they can for their cell, even + if it is full. + - bugfix: Vampire shapeshifting should now behave as intended + - balance: Some synth damage stuff has been a bit rebalanced, see the PR for details. + - rscadd: Nanogel, available at medical and robotics, which fixes internal damage + in sufficiently repaired robotic limbs. + - balance: Robotic Limbs now each have their own damage threshhold values + - balance: Robotic Limb damage threshholds are now seperated into threshhold itself + and mindamage when passed balance; Hybrid limbs can now be injected with hypos, + but not sprayed (Still not healed by chems) + - tweak: Brain surgery has been tweaked back to allowing robotic limbs, blacklisting + IPC brains instead. + - tweak: Robot brain surgery can now be used on organic heads, if there is a IPC + brain in them somehow. + - tweak: The robot limb heal surgery can now be used even if the target's torso + is not robotic, as long as they have robotic limbs + - refactor: BODYPART_ROBOTIC / BODYPART_ORGANIC checks replaced with helper-procs + whereever possible. + - code_imp: Added a BODYPART_HYBRID define for robotic bodyparts that behave organic + in some regards. + - bugfix: The transmission sigil power drain works now + - bugfix: A certain lizard (totally not me) being stupid is no longer going to break + regenerate_bodyparts + - bugfix: Combat mode now will not stay permanently disabled due to status effects + not working as intended. + - bugfix: Attacking some certain objects no longer has no clickdelay. + - bugfix: the blacksmithing skill now works properly + - tweak: Anvils cannot be interacted with with hammers whilst they are already being + used + - tweak: If someone has no gloves when interacting with heated ingots, they no longer + ignore their effects. + - bugfix: A runtime caused by hallucinations is gone. + - bugfix: Cargo packs marked as 'no private buying' now actually register as such. + - balance: Fleshmend, Anatomic Panacea and bloodsucker healing now work for Synths + / IPCs. + - tweak: Medibots now ignore people they cannot help due to their biology. + - bugfix: get_damaged_bodyparts() is no longer broken. + - bugfix: Your target cryoing will no longer give you a free greentext. + - bugfix: Sleeper UI interactiveness now behaves correctly. + Detective-Google: + - rscadd: arcade carpet + - rscadd: explosions now get broadcasted to deadchat. + - rscadd: Lick radial + - bugfix: Hilbert's jukebox works + - bugfix: arcade carpets now actually work + - tweak: the snow taxi is no longer the slow taxi + ERP mains: + - rscadd: Subtler Around Table is now a verb + EdgeLordExe, MoonFalcon: + - balance: Ported a bunch of heretic-related tweaks and changes from tg + EmeraldSundisk: + - rscadd: Adds a few new area designations primarily for CogStation, incorporates + them into said map + - tweak: Reorganizes some area designations for ease of use, along with renaming + the central "Router" to "Routing Depot" + - bugfix: Fixes an incorrectly designated area in CogStation + - bugfix: Changes the area designations to be not varedited since the code didn't + like that anymore + - bugfix: The cargo bay conveyor belts not only work with the shuttle now but go + in the right direction to boot + - tweak: Slight visual adjustments to cargo in light of this + - rscadd: The arcade's got RAD carpet now + - bugfix: Fixes the conveyor belt issues in Delta Station's cargo wing + - rscdel: Removes some of the dirt around the affected area (presumably they would + have cleaned it up while working on it) + - rscadd: Adds a floor light to fix the "dark spot" cargo had + - rscadd: Adds a new "Computer Core" area designation for CogStation + - bugfix: Fixes some missing area strings + - tweak: Replaces some firelocks with directional ones as to ensure desks/counters + can still be accessed + - balance: The "Skelter ruin" now has stechkins as opposed to M1911s + - tweak: Skelter's decorative bullet casings replaced to factor in the change in + caliber + - rscadd: Skelter now has a combat knife and fluff note + Ghommie: + - bugfix: You can access the mime / clown mask skins radial menu once again. + - bugfix: Dice bags no longer act like cardboard boxes. + - bugfix: Abductors should be no longer mute. + - bugfix: Item action buttons should now properly show the item current overlays, + most times. + - bugfix: The blackbox should now go into your hand slot when pried out, rather + than tumbling on the ground everytime. + - tweak: The Quick Equip hotkey is now usable by all living mobs (so long they have + hands and equipment slots) + Ghommie, porting PRs by MMMiracles and pireamaineach, credits to BlueWildrose too.: + - rscadd: You can now draw on plasmaman helmets with a crayon to turn their frown + upside-down. + - balance: Plasmaman helmets no longer hide your identity when worn by themselves. + - balance: Plasmaman helmets now have welding visors, which can't stack with their + torches in the helmet and are visible. + Hatterhat: + - rscadd: Energy sabre reskin for the energy sword - access via alt-click. + - bugfix: Alt-click reskins are fixed. + - tweak: Defibrillators and their many, many overlays were moved to another .dmi. + - tweak: You can now change the color of an energy sword via multitool. Not deswords. + Yet. + - imageadd: The Syndicate appear to be issuing new revolver variants. + - rscadd: Basic sticky technology is now a roundstart tech. Advanced sticky technology + is BEPIS-locked, though. Theoretically. + - tweak: Non-smithed katanas (including the temporal katana) can now fit in the + twin sheath. + - tweak: Cotton and durathread processing by hand now acts like grass. Stand on + a pile of cotton (or durathread) and use a single bundle from it. + - spellcheck: Utility uniforms now comply with the "nonproper equipment names" thing. + - bugfix: The CapDrobe now allows the captain to get his own clothes for free. Probably. + - tweak: All captains' clothes now offer 15 woundarmor, up from the 5. Because apparently + only the suit and tie and its suitskirt subtype have this wound armor, which + is dumb. + - rscadd: The nature interaction shuttle with the monkeys now has tiny fans on the + airlocks in, because that's apparently a feature that was missing. + - rscadd: More bags have been added to department vendors. + - balance: Every roundstart species (and also ash walkers) now has flesh and bone + that can be wounded. + - balance: Recipes for sutures, regen mesh, and sterilized gauze have been adjusted + to be easier, mostly. + - balance: Sterilized gauze is better at absorbing blood and being a splint. + - bugfix: Energy sabres now have an off inhand. + - balance: The bone gauntlets should be slightly less murderously punchy on the + fast punches mode. + - tweak: RPEDs now drop their lowest part tier first when quick-emptied (used inhand). + - tweak: Improvised gauzes can now be crafted in stacks up to 10, like their maximum + stacksize implies they should be capable of doing. + - bugfix: Pouring sterilizine on gauze now takes the proper 5u per sterilized gauze + instead of 10u. + - bugfix: Cryogenics now screams on common again when your fuckbuddy heads out. + - rscadd: Survival daggers! A slightly more expensive survival knife that comes + with a brighter flashlight. On the blade. + - tweak: Luxury pod capsules look different from normal capsules. + - rscadd: The wastes of Lavaland and the icy caverns of Snow Taxi rumble in unison. + - tweak: Exosuits sold on the Supply shuttle no longer leave wreckages. + - rscdel: Apparently, shrink rays were buyable again, despite a PR having been made + a while ago specifically for removing shrink rays. They're gone again. + - rscadd: Changeling bone gauntlets! They punch the shit out of people really good. + - tweak: Guerilla gloves and gorilla gloves inherit the strip modifiers of their + predecessors, because apparently they had those. + - balance: Pugilists now always hit the targeted limb and never miss. + - rscadd: The dock-silver standard set by Box and Meta has been enforced across + maps in rotation (Delta, Pubby, Lambda). + - bugfix: The Box whiteship now has its missing tiny fan back. + - bugfix: The survival dagger light on the sprite now actually turns on and off. + - balance: The survival dagger in the glaive kit that can also be bought by itself + is now better at butchering things. + HeroWithYay: + - bugfix: Changed description of Necrotizing Fasciitis symptom. + - tweak: Wormhole Projector and Gravity Gun now require anomaly cores to function + instead of firing pins. + KeRSedChaplain: + - imageadd: Resprited the brass claw + LetterN: + - rscadd: 2 more ways to get up from z1 + - tweak: tweaked the z2 garden to be less blank + - bugfix: fixed telecomms pda log + - rscadd: Coin & Holochip support for slot machine + - admin: Stickybans are now saved in the DB too + - soundadd: Immersive ™ audio reverbs. (also adds multiz audio) + - code_imp: Semi-hardsync from TG + - code_imp: Updates rust-g + - code_imp: Uses git CI instead of travis/appveyor now + - code_imp: Updates git and build tests. + - bugfix: minimap text + - code_imp: ports cinematic upgrades + Linzolle: + - bugfix: entertainment monitors no longer invisible + - rscadd: entertainment monitors now light up and display text when motion is detected + in thunderdome + - bugfix: lizard snouts are no longer *slightly* lighter than they are supposed + to be. + MrJWhit: + - rscadd: Expanded space hermit base + - tweak: Replaced engineering fuel tank with a large fuel tank + - tweak: Changed access to sec suit storage from armory access in every map to other + security access + - rscadd: Adds a space loop to every map in toxins + - rscadd: '' + - tweak: Added the ability for cargo to buy a large welding tank + - imageadd: Tweaked large tank reagent sprites to /tg/'s + - tweak: Gives metastation toxins storage a scrubber and a vent + - tweak: Updates suit storage info on Tip Of the Round. + - tweak: Increased christmas event from 22th to 27th to 10th to 27th + - tweak: Removes an opposum from the wall + - tweak: Donut boxes show what's inside of them now + - tweak: Updated meat icons + - admin: Canceling events gives more time to stop from 10 to 30 + - tweak: Fixes two chairs on one table + - tweak: Removed the wires connecting the AI from the rest of the station on cogstation. + - tweak: Fixes experimenter on cogstation. + - tweak: Less pipes in the overall area in toxins on cogstation + - tweak: Small fixes on security on boxstation + - tweak: Updated jukebox sprite. + - tweak: Fixes maint area in boxstation + - tweak: Christmas starts on the 18th now + - rscadd: Adds a goose bar sign + - bugfix: Effects can no longer trigger landmines + - rscdel: Removes the screen flashing on climax. + - rscadd: Makes gas sensors fireproof. + - tweak: A small bucket of random fixes, + - tweak: Minor fixes to kilo + - tweak: Porting garbage collection tweak from /tg/ + - tweak: Updates our dark gygax sprites to /tg/'s + - tweak: Bugfix of a morph becoming an AI eye + - tweak: Mining station oxygen locker on the cycling airlock starts out wrenched. + - balance: Nerf combat knife damage + - bugfix: Code improvement on ventcrawling + NT Cleaning Crews On Break: + - rscadd: Most kinds of dirt, grime, and debris are now persistent. Get to work, + jannies. + - rscadd: Dirt can now be removed by tile replacements. Other cleanable decals can't, + though. + Putnam3145: + - tweak: Replaces majority judgement with usual judgement. + - bugfix: Toilet loot spawners don't lag the server on server start with forced + hard dels. + - bugfix: vore prefs save now + - tweak: gear harness no longer magically covers up the body mechanically despite + covering up nothing visually + - balance: Regen coma now puts into a coma even from crit or while unconscious. + - bugfix: Regen coma now properly weakens while asleep. + - bugfix: Multi-surgery unit test no longer fails at random. + - refactor: Dwarf speech is no longer absolutely paranoid about word replacement. + - balance: Spontaneous brain trauma now requires minimum 5 players + - tweak: Grab bag works as advertised. + - balance: Xeno threat in dynamic tripled. + - code_imp: 'Vote system #defines are now strings' + - rscadd: Stat panel UI for ranked choice votes + - rscadd: A fallback for dynamic antag rolling that allows for it to just try between + traitor, blood brothers, heretics, changeling, bloodsucker and devil until there + are enough roundstart antags. This can also happen randomly anyway. Blood brothers + and devil are disabled for now, but the code is there to enable them. + - rscadd: A new storyteller, "Grab Bag", that forces the above round type. + - bugfix: atmos subsystem no longer dies if there's too many gases + - bugfix: Emotes can properly be filtered for in TGUI. + - bugfix: Holofirelocks work now. + - bugfix: adminhelping no longer removes entire admin tab + - bugfix: end of round no longer removes entire admin tab + - bugfix: Fixed a runtime in every healing nanite program. + - bugfix: removed a unit test causing master to fail + - tweak: Planetary atmos no longer does superconduction. + - bugfix: Dynamic vote no longer shows the none-storyteller. + - tweak: You can now exit polycircuit input + - bugfix: Polycircuits now check for range + - bugfix: gear harness alt-click is now sane + - code_imp: rolldown() and toggle_jumpsuit_adjust() now no longer mix behavior-that-should-be-overridden + and behavior-that-shouldn't-be-overridden in ways that make no sense. + - tweak: Gear harness now covers nothing. + - bugfix: Chemical stuff now displays fermichem stuff properly + - balance: Rad collectors now get 1.25x as much energy from radiation + - balance: Rad collectors now put out 1.25x as much stored energy per tick + - balance: Above two rad collector changes give a total 56.25% power output increase + - balance: Zeolites now only generate 1/5 the heat when reacting and don't require + a catalyst. + Ryll/Shaps: + - admin: Fixed an issue with player logs becoming confused when someone triggers + multiple events within one second (like being attacked by two people at the + same time) that would cause holes in the logs + SandPoot: + - tweak: You can attack a pile of money on the floor with your id to put it all + in quickly. + - refactor: Changes the limb grower a lot. + - bugfix: '"Limb" costs on limbgrower are actually displayed like it was meant to + all along.' + - code_imp: Swaps the gift static blacklist with a global list one. + SiliconMain: + - tweak: Engi department has gas masks in loadout + - tweak: hololocks (which haven't worked for god knows how long) commented out until + auxmos is merged + Sonic121x: + - rscadd: alarm ert hardsuit sprite for naga and canine + - tweak: adjust the naga ert hardsuit to cover the hand + - bugfix: cydonia hardsuit helmet + - rscadd: digi sprite uniform + - bugfix: digi leg suit + SpaceManiac: + - bugfix: Fixed the maphook + Thalpy: + - bugfix: fixes some bugs in jacqs code from edits to the codebase + The Grinch: + - rscdel: infinite presents from hilbert hotel + TheObserver: + - rscadd: Re-adds the rifle stock, and sets the improv shotgun to be as it was. + - rscdel: The maintenance rifle has been shelved - for now. Watch this space. + TheObserver-sys: + - bugfix: Drake? Where's the dead fairygrass sprite? + TheSpaghetti: + - bugfix: no more tumor bread double punctuation + Trilbyspaceclone: + - tweak: Zeolites now use gold rather then uranium for catalyst + - tweak: Zeolites are not as hard to make ph wise + - tweak: Making Zeolites heats up the beaker less allowing for better control + - tweak: ASP 9mm and M1911 can now have suppressers added + - balance: Brass welders are 50% faster at refueling + - code_imp: redoes self fueling welders in the code to be less speggie + - rscadd: the corporate unifoms can now be gotton in the clothing mate vender + TripleShades: + - rscadd: 'Firelock to Surgery Bay drapes change: Swapped Nanomed and Fire Alarm + button locations in both Surgery Bays change: Removes the double mirror in both + Surgery Bays to be a singular mirror change: Moved an intercom to not be doorstuck + below Paramedical Office remove: One Surgery Observation Fire Alarm button' + - rscadd: 'New Paramedic Office next to Genetics where the old Genetics Reception + used to be change: Surgery, Surgery Observation, and Recovery Hall layout revamped + drastically change: Maints below Surgery lowered by one tile to recover lost + tile space from Surgery expansion' + Tupinambis: + - rscadd: Arachnids (spider people) with limited night vision, flash vulnerability, + and webbing. + Vynzill: + - rscadd: 'new gateway mission mapadd: jungleresort map' + - bugfix: fixes high luminosity eyes + Xantholne: + - bugfix: Fixed new birds changing back to basic parrot when sitting + - rscadd: New parrots from the RP server, can be found in Bird Crate in Cargo + - rscadd: You can now tuck disky into bed + - rscadd: You can now make beds by applying a bed sheet to them + - rscadd: You can now tuck in pai cards into bed + - rscadd: Added bed tucking element, can be added to any held object to allow tucking + into beds + - bugfix: Twin Sword Sheaths have an equipment icon and icon when worn now and make + a sound when sheathed/unsheathed + Yakumo Chen: + - balance: Slime Jelly is no longer obtainable from slimepeople. Go ask Xenobio + YakumoChen: + - balance: "To lower production costs, Buzz Fuzz is now manufactured with Real\u2122\ + \uFE0F Synthetic honey." + Zandario: + - code_imp: Added some framework for future species expansions, including clothing + refitting. + - refactor: Made majority of the relevant Species IDs and Categories pre-defined, + also for easier expansion and use. + - bugfix: lum slime sprites work again + - code_imp: Slapped the Species Defines where relevant + corin9090: + - tweak: The chaplain's prayer beads can now be worn on your belt slot + kappa-sama: + - bugfix: super saiyan + - tweak: ishotgun crafting recipe no longer requires plasteel and is slightly more + convenient + - balance: ishotgun does 45 damage now instead of 40.5 + - rscadd: s + - tweak: s + - balance: s + - bugfix: s + - rscadd: A new spell for the wizard and his martial apprentices, the Inner Mantra + technique. It makes you punch people really good and makes you durable, but + drains your energy while it's active. + - rscadd: A self-buffing spell for valiant bubblegum slayers that is ultimately + useless on lavaland and probably overpowered for miner antagonists. Go figure. + At least all it does is let you punch hard while draining your health every + second. + - balance: bubblegum now drops a book that makes you into an abusive father instead + of a shotgun that plays like pre-nerf shotguns + - soundadd: a powerup and powerdown sound effect + - imageadd: two icons for two buff spells + keronshb: + - bugfix: Allows Energy Bola to be caught + - balance: This also allows them to be dropped/picked up. + - rscadd: Adds a reduced stamina buffer for SCarp users + - rscadd: Gives SCarp users a better parry + - rscadd: Adds the SCarp bundle which includes a bo staff + - rscadd: Lets Carp costumes carry Bo Staffs + - balance: reduces the stamina damage of scarp slightly + - balance: reduced the blockchance of the bo staff + - rscadd: Adds more room to northwest maint + - rscadd: Adds a bridge between Atmos and the Turbine. + - balance: Blob Resource Tower to 2 points per instead of 1 point per. + - balance: Blob Factory Towers can be placed 5 tiles apart instead of 7. + - bugfix: Fixes Blobbernaut Factories consuming Factories if no naut is chosen. + - bugfix: Fixes Reflective Blobs + - rscadd: Re-adds the Clown Car to the clown uplink + - balance: 15 >16 TC cost + - balance: bonks on external airlocks + - bugfix: Fixes the parry data for scarp + kittycat2002: + - rscadd: set the name of /datum/reagent/consumable/ethanol/species_drink to Species + Drink + kiwedespars: + - balance: balanced bone gauntlets. + - rscadd: the robust dildo weapon now has sound. + necromanceranne: + - bugfix: Fixes various sprites for bokken, as well as being unable to craft certain + parts and duplicate entries. + - rscadd: 'Bokken now come in two lengths; full and wakizashi, and two varieties: + wood and ironwood. They have different stats for all four.' + - rscadd: Bokken require menu crafting and part construction, as well as more complicated + materials. + - tweak: Bokken (long and short) require wood, cloth and leather to craft with a + hatchet and screwdriver. + - tweak: Ironwood bokken (long and short) require ironcap logs, cloth and leather + to craft with a hatchet, screwdriver and welder. + - balance: Twin sheathes can only fit a pair of blades (longsword + shortsword) + or they can fit two shortswords. + - bugfix: Fixed a twin sheath runtime. + - imageadd: A lot of bokken related sprites received an overhaul. Added overlay + sprites for weapons sheathed in the twin sheathes. + - imageadd: The extradimensional blade received improved sprites for inhands/back + sprites. + - bugfix: You can now make all the variants of the bokken. + - bugfix: Removes a duplicate sprite. + - tweak: Renames all instances of 'ironwood' to 'steelwood'. + - rscadd: Adds new roboticist labcoat sprites! + qwertyquerty: + - bugfix: Flash the screen on climax + raspy-on-osu: + - spellcheck: salicylic acid + - tweak: space heater heating range and power + - tweak: windoor open length + shellspeed1: + - rscadd: Wings from Cit RP have been ported over + - rscadd: Moth wings from cit have been ported over + - bugfix: Cleaned up some pixels on existing moth wings. + - tweak: Organized the lists for wings by if they are for moths or not and than + by alphabetical. + - balance: Lings now have infinite space for DNA. + - rscadd: All xenomorph types have been added as corpses for mapping purposes + - balance: The dead xenomorphs in the lavaland xenomorph hive now have more variety. + - tweak: Floor bots are now buildable with all toolboxes. + - rscadd: 'Xenomorph hybrids can now select wings ~~add: Xenomorph hybrids can now + speak xenomorph~~' + - rscadd: Xenomorph tongues are available for customization. + - rscadd: Mining borgs can claim points again + - rscadd: Construction bags have been added, use them to carry all sorts of construction + bits. + - rscadd: A recipe has been added to cloth stacks to make material and construction + bags. + - balance: Material bags and construction bags are now available in engineering + lockers. + - rscadd: Adds the disposable sentry gun from tg for 11tc each. + - rscadd: The exofab can now print prosthetic limbs + - bugfix: The exofab was missing access to multiple cybernetic organs. This has + now been rectified. + - rscadd: A new recipe for a spicy has been given to us by a strange business man. + - rscadd: The bluespace navigation gigabeacon design has been added to shuttle research + for those wanting to take their ships around space more. + - tweak: Xenomorph powers now list plasma cost in their description. + silicons: + - tweak: nanite resistances tweaked + - rscadd: new nanite programs added for locking the user out from being modified + by consoles or antivirals. + - rscdel: anomalies no longer spawn in walls + - rscadd: 'Twitch Plays: Clown Car' + - rscadd: pugilists can now parry + - balance: c4 can no longer gib mobs + - tweak: medium screens are better now + - tweak: text formatting now uses one character instead of two around the text to + emphasize. + - rscadd: colormates + - balance: shoving yourself up now costs 50% more + - bugfix: dullahans enabled + - rscadd: tailed individuals can now target groin to intertwine tails on grab intent. + - rscadd: Clowns now have unpredictable effects on supermatter crystals when dusting + from contact. + - tweak: anyone new to the server is lucky enough to have their sprint default to + toggle instead of hold + - balance: stamina crit is only removed when at or under 100 stamina, rather than + 140. stamina crit threshold is still at 140. + - tweak: luxury shuttle no longer has noteleport + - rscdel: now only poly gets a headset on spawn, not all birds. + - tweak: the warp implant now actually warps you back 10 seconds. leaves a trail, + though. now unlimited us. + - bugfix: things in DEATHCOMA do not deathgasp on death + - tweak: Meth and changeling adrenals no longer ignore all slowdowns, rather damage + slowdowns. + - rscadd: you can now be an angel using a magic mirror again + - tweak: command headsets are 120% instead of 160% + - bugfix: no more emote italics + - rscadd: players can now respawn/return to lobby as a ghost after a 15 minute (default) + delay and rejoin on another character with some/many restrictions + - rscadd: cryo now preserves everything + - bugfix: Magrifle ammo no longer glows. + - tweak: temperature slowdown divisor nerfed to 35 from 20. + - balance: dna melt drops all items being destroying you + - bugfix: keybinds generate anti-collision bindings where necessary automatically + now + - balance: changeling combat mutations rebalanced. most of them take chemicals to + upkeep now. + - rscadd: set-pose has been added + - tweak: temporary flavor text renamed to set pose, fully visible in examine + - bugfix: ninja gloves no longer hardstun + - balance: ninja gloves now cost half as much to use to compensate + - bugfix: simple mobs are now immune to radioactive contamination + timothyteakettle: + - bugfix: time for memory loss message to show up when being revived is now correctly + 300 seconds, instead of 30 + - bugfix: the load away mission verb won't crash the server now + - rscadd: roundstart slimes can turn into puddles now + - rscadd: all gas masks (but welding + glass) can be alt clicked to show/hide identity + - tweak: autosurgeons from travelling trader rewards now only have one use + - bugfix: fixes held items proccing crossed when passing someone + - tweak: you can now get a family heirlooms based off your species instead of job + - tweak: changeling stings retract upon turning into a slime puddle + - tweak: you cannot transform into a slime puddle with a no drop item in your hands + - tweak: slime puddles are now transparent and their colour looks more natural in + comparison to the user + - tweak: slime puddles are now even slower + - tweak: slime puddles now get no protection from worn clothing + - rscdel: removes two debug messages left in from my prior eye customization pr + - rscadd: adds unlockable loadout items, corresponding category in loadouts, etc + - rscadd: added in-game age verification as an alternative to access requests + - bugfix: disabling adminhelp noises no longer disables looc + - bugfix: apids render now + - bugfix: you can now only entwine tails with people who have a tail + - bugfix: custom eyes and tongues now properly carry across cloning + - rscadd: re-adds the holoform verb for people who want to use it over going through + the char list + - bugfix: eye sprites should look normal once more + - rscadd: licking people washes pie off their face + - rscadd: you can now pick your eye sprites from customization + - tweak: looking at loadout equips loadout items on your preview image instead of + job items + - tweak: custom holoforms are now accessible through an action instead of through + verbs + - tweak: AI holoforms can now emote + - tweak: cloning now correctly copies your blood colour, body sprite type and eye + type + - bugfix: species with NOTRANSSTING cannot have envy's knife used on them + - rscadd: avian/digitigrade legs have been added for slimes + - rscadd: you can teleport bread + - tweak: slime puddles are no longer layered down one layer + - tweak: you cannot tackle with two paralysed arms + - tweak: tackling with a single paralysed arm lowers your tackle roll by 2 + - bugfix: circuits get pin data proc is sanitized when text is returned as data + - rscadd: loadout now has save slot support and colour choosing/saving for polychromic + items + - rscadd: polychromic maid outfit + - rscadd: you can rebind communication hotkeys and they're the default now + - rscadd: you can now customize your size from 90% to 130%, going below 100% makes + you have 10 less max health + - rscadd: '*squeak' + - rscadd: anthromorphic synth species + - rscadd: improvements to the automatic age gate + - tweak: antag items are now of critical importance and wont fail to be placed on + the character + - bugfix: a tonne of fixes to colourisation of parts, too many to name, including + some sprite fixes + - rscadd: things now have their own individual primary/(secondary)/(tertiary) colours + as required, and these can be modified by you + uomo91: + - bugfix: Fixed "Show All" tab in player panel logs being broken. + - bugfix: Whispers, OOC, and various other things display differently in logs, visually + distinguishing them from say logs. + - refactor: Player panel logs will now show all logs chronologically, so you'll + see commingled say and attack logs if you're on the "Show All" tab, etc... + yorii: + - bugfix: fixed botany rounding error that caused grass and other plants to misbehave + zeroisthebiggay: + - bugfix: legion now drops chests + - rscadd: Traitor assistants can now purchase the patented POGBox! Put TC into it + for even higher damage! + - balance: MEGAFAUNA DROPS ARE LAVAPROOF + - imageadd: cool codex cicatrix inhands + - rscadd: gravitokinetic stands from tg + - balance: buffs stands overall + - bugfix: protector stands no longer become tposing invisible apes sometimes + - bugfix: jacqueline spawns on boxstation + - rscadd: secsheath for your cool stunsword at your local security vendor. you gotta + hack it first though. + - imageadd: fuck the r*d cr*ss + - rscadd: The legion megafauna has been reworked. The fight should now be both slightly + harder and faster. + - balance: You can no longer cheese the colossus by being a sand golem and simply + being immune. +2021-01-22: + Arturlang: + - rscadd: Adds a way to give items to people, you can combat mode rightclick to + offer it to one person, right click on people without mode and click the give + verb, or use the hotkey CTRL G to offer it to everyone around you +2021-01-25: + MrJWhit: + - bugfix: Alien radio code + - rscadd: Microwave can now be cleaned by a damp rag as well as soap. + - bugfix: Removes some unused code, and improves some other code. + - rscadd: The AI has a verb to look up and down z-levels + - bugfix: Making a monkey into a human doesn't unanchor random things on the tile + - bugfix: Makes a few slight improvements to drinking code + - tweak: Makes encryption keys be put in the hands of the user when able instead + of being dropped on the floor when removed from headsets + raspy-on-osu: + - refactor: ventcrawling + silicons: + - tweak: you can now shove yourself up in any intent, not just help. +2021-01-27: + ArcaneMusic, ported by Hatterhat: + - rscadd: Strike a hydroponics tray with a fully-charged floral somatoray to lock + in a mutation. + - rscadd: Floral somatorays now have the ability to force a mutation in a plant. + This should drain the cell in a single shot, but we'll see. + - balance: Somatorays now take uranium to craft instead of radium. + Arturlang: + - rscadd: Actually adds a right click give option + - rscadd: Revenants can now clickdrag to throw stuff at people, with some items + doing various things at the same time. + DeltaFire15: + - bugfix: The woundmending rite no longer causes runtimes. + - bugfix: Ratvarian borgs can now use their tier-0 spells. + - balance: Ratvarian borgs can always use their assigned spells, if there is enough + power. + - admin: The heretic antag panel now shows their sacrifices & current sacrifice + targets. + - tweak: The heretic roundend report now shows their sacrifices and nonsacrificed + targets. + - bugfix: Living hearts can no longer select the same target as another living heart, + removing a certain problem. + Hatterhat: + - tweak: Department budget cards have been readded. TO THE CODE. NOT LOCKERS. + - tweak: Also budget cards now look more like every other ID - see tgstation#55001. + - balance: One of the contractor tablet's payouts has been raised from a small payout + to a medium payout. + - balance: The free golem ship's GPSes no longer start on. They were never meant + to, but they did. + - rscdel: Headsets can't be found on most legion corpses now. + - rscdel: The flash on the assistant corpse is gone, too. + MrJWhit: + - tweak: Remaps some air alarms for sanity. + SandPoot: + - bugfix: The drop circuit can no longer drop things that are not inside it. + raspy-on-osu: + - bugfix: bespoke ventcrawling element not detaching due to malformed call + shellspeed1: + - bugfix: Floorbots had had a software update, preventing them from dogpiling on + their target as easily as they did before. + - soundadd: Floorbots will now play a small chime when stacked on top of each other + to indicate that they're moving apart. + timothyteakettle: + - rscadd: blobs can use the 'me' verb + - admin: adminhelps and pms only sanitize once instead of twice +2021-01-28: + silicons: + - rscadd: colormates can now paint some mobs. + - bugfix: 1 dev explosions shouldn't delete brains anymore +2021-01-29: + MrJWhit: + - tweak: Ported the QM, Captain, CMO, and HoS cloaks from beestation. + - rscdel: Removes excess air alarms from boxstation + TripleShades: + - bugfix: fixes engineering secure storage being the wrong area because I fucked + that up previously my bad + - bugfix: removes funny extra light switch under right surgery table in surgery + oops + - rscadd: Added chairs to the corpse launch viewing area + - rscadd: Small garden plot for flowers for parity with other station Chapels + - rscadd: Plain Bible to glass tables in Chapel + - rscadd: Candles and Matchbox to glass tables in Chapel + - rscadd: More glass tables, with a chaplain figure and another spare bible. + - rscadd: Bookcase to Box Chapel for parity with other station Chapels + - rscadd: Minimoog to Box Chapel as substitute for a church organ + - rscadd: 'Holy department sign just below Chapel change: Expanded the corpse launching + area to feel less congested change: Added windows to the corpse launch so you + can look inside I guess? change: Moved flowers and burial garments to the corner + next to the corpse launcher change: Box Chaplain''s office door is moved over + one change: Confessional is now connected to Chaplain''s office for parity with + other station Chapels change: Moved coffins over to old confessional location + change: Box Chapel now has pews instead of stools change: Box Chapel Confessional + is now lit instead of being nearly pitch black remove: Two coffins from Chapel' + timothyteakettle: + - bugfix: the miner bedsheet will now increment its progress when you redeem points + from the ORM + - rscadd: you can add custom names and descriptions to item's on the loadout now + zeroisthebiggay: + - rscadd: roundstart aesthetic sterile masks and roundstart paper masks + - rscadd: more accessory slot items + - rscadd: cowbell necklace happy 2021 + - rscadd: shibari ropes & torn pantyhose +2021-01-30: + timothyteakettle: + - rscadd: adds 'clucks', 'caws' and 'gekkers' to the speech verb list + zeroisthebiggay: + - rscadd: some more FUCKING hairs + - imageadd: uncodersprites the advanced extinguisher +2021-01-31: + Putnam3145: + - balance: fermichem explosion EMPs don't cover the entire station +2021-02-02: + silicons: + - rscadd: pais can now be carried around piggybacking/fireman + - balance: Meth and Nuka Cola once again, speed you up. +2021-02-03: + Hatterhat: + - bugfix: The green energy sabre's sprite now respects proper handedness. +2021-02-05: + SmArtKar: + - rscadd: The orbit menu now has an Auto-Observe button! No more sifting through + the lame observe menu to snoop in people's backpacks! Also, orbit menu now refreshes. + - bugfix: KAs are no longer getting broken when fired by a circuit + keronshb: + - balance: Force and damage > 15 from 18/25 + - balance: Knockdown put down to 5 from 30 + - balance: Armor pen down to 10 from 100. + - balance: Makes cell chargers, charge faster. + raspy-on-osu: + - bugfix: alien royals can no longer ventcrawl + shellspeed1: + - balance: There actually needs to be people for zombies to happen now. + timothyteakettle: + - rscadd: dwarf facial hair is no longer randomised +2021-02-07: + Thalpy: + - refactor: 'Dispenser: Adds the ability to store a small amount of reagents in + the machine itself for dispensing. Reacting recipies cannot be stored. Size + of storage increases with bin size.' + - refactor: 'Dispenser: Allows reagents to be color coded by pH' + - refactor: 'Dispenser: Each reagent displays it''s pH on hover' + - refactor: 'Dispenser: Allows the user to toggle between buttons and a radial dial' + - refactor: 'Dispenser: When the dispencer is upgraded it can dispense 5/3/2/1 volumes + based on rating refactor: Dispenser: as it was before. This does not break recorded + recipes.' + - tweak: Adds a round function to some numbers so they're not huge + - tweak: The Chem master can now get purity for all reagents when analysed + - bugfix: Synthissue fixes + - tweak: buffers now have a strong and weak variant. Weak can be dispensed, and + strong can be created. Strong buffers are 6x more effective. + - bugfix: Some buffer pH edge calculation fixes + TyrianTyrell: + - rscadd: added a signed language, that can't be used over the radio but can be + used if you're mute. also added the multilingual trait. + - imageadd: hopefully added an icon for the signed language. + - code_imp: changed how some traits function slightly. + dzahlus: + - tweak: tweaked a few sounds + - soundadd: added a new weapon sounds + - sounddel: removed old weapon sounds + - code_imp: changed some sound related code + silicons: + - rscadd: syndicate ablative armwraps have been added. +2021-02-09: + Chiirno: + - rscadd: Adds clown waddle to clown shoes. Enhanced Clown Waddle Dampeners can + be engaged in-hand with ctrl+click, _but why would you?_ + MrJWhit: + - rscadd: Re-adds theater disposal outlet, and makes dorms disposal able to have + things sent to it on boxstation. + TyrianTyrell: + - bugfix: made default tongue able to speak signed language. + timothyteakettle: + - balance: sentient viruses can now infect synths and ipcs +2021-02-11: + Adelphon: + - rscadd: Charismatic Suit + - rscadd: Urban Jacket + DeltaFire15: + - tweak: Added nanogel to the robodrobe. + Putnam3145: + - rscadd: Config to keep unreadied players from mode voting + dzahlus: + - bugfix: fixes grenadelaunch.ogg being used where it shouldn't and makes mech weapons + use correct sound + keronshb: + - balance: 10 > 30 second for Warp Implant cooldown + - rscdel: Comments out power sink objective. + timothyteakettle: + - bugfix: persistent blood should stop being invisible and alt clicking it shouldn't + return the entire spritesheet + - admin: pickpocketing is now logged using log_combat + zeroisthebiggay: + - tweak: the aesthetic sterile mask no longer hides faces so you can cosplay egirls + and keep flavortexts +2021-02-12: + Hatterhat: + - balance: The ATVs on SnowCabin.dmm have been replaced with snowmobiles. + MrJWhit: + - tweak: Random deltastation fixes. + - tweak: Gives boxstation vault door actual vault door access + silicons: + - balance: Voice of God - sleep removed, stun staggers instead, knockdown is faster + but does not do stamina damage, vomit is faster but doesn't stun +2021-02-13: + Hatterhat: + - balance: Energy bolas now take 2.5 seconds to remove and dissipate on removal. + timothyteakettle: + - admin: migration error to version 39+ of savefiles is now logged instead of messaging + all online admins in the chat +2021-02-14: + DeltaFire15: + - admin: The antag panel now correctly shows the names of cultist / clockcult datum + subtypes. + - bugfix: Adding clock cultists via the admin panel now works correctly. + - bugfix: Xeno larvae should now be able to ventcrawl again. + Hatterhat: + - tweak: Repairing sensors on jumpsuits now requires a fully-intact jumpsuit. Find + some cloth. + - tweak: Durathread armor kits now require you to have a fully-repaired jumpsuit, + first, with no attachments. + - bugfix: Durathread armor kits now no longer weave the entirety of the jumpsuit + armor universe into having armor. + TyrianTyrell: + - code_imp: added a define for multilingual granted languages, and changed the multilingual + trait to use it. +2021-02-15: + Adelphon: + - rscadd: polychromatic shoes + - rscadd: polychromatic windbreaker + - rscadd: polychromatic canvas cloak + - bugfix: digitigrade charismatic suit texture + DeltaFire15: + - balance: Kneecapped pugilist parries somewhat. + - balance: Slightly nerfed default unarmed parries. + - balance: Slightly nerfed traitor armwrap parries. + - bugfix: Pugilist parries now cannot perfectly defend against projectiles, as they + were supposed to. + - bugfix: Some parrying numbers that one would think were in seconds didn't have + the SECONDS. I added those. + - balance: Clock cultists now yell alot less when invoking scripture. + dzahlus: + - rscadd: Added new emote + - soundadd: added a new emote sound + silicons: + - balance: people on the ground hit less hard in unarmed combat. rng miss remove + from punches. + - bugfix: chat highlighting no longer drops half your entered words. +2021-02-16: + silicons: + - config: sprint removal entry added, UI will revert to old UI while this is active. +2021-02-18: + BlueWildrose: + - admin: Admins now receive messages regarding certain holodeck actions. + Hatterhat: + - bugfix: Free Golem Ship GPSes now start as disabled. Like they were supposed to. + LetterN: + - tweak: No more liver damage when you opt out of "hornychems" + SmArtKar: + - rscadd: Added a new TCG card game + dzahlus: + - rscdel: Removed maroon objective due to toxic gameplay behaviour + shellspeed1: + - bugfix: floor bots place plating before tiles now. + - bugfix: gets rid of another tile duplication issue. + silicons: + - spellcheck: priviledge --> privilege +2021-02-19: + Putnam3145: + - bugfix: Buzz Fuzz's addiction threshold is now a can and a sip as intended. + timothyteakettle: + - admin: staring into pierced realities is now logged +2021-02-20: + Adelphon: + - rscadd: polychromic pants + - tweak: urban coat made polychromic + Chiirno: + - tweak: Synthflesh now unhusks with 100u instead of requiring 101u. + SmArtKar: + - tweak: Added some QoL changes to TCG + - bugfix: Fixed TCG cards not saving + TyrianTyrell: + - bugfix: fixed the signed language so that you can actually use it, and that it's + unusable when it's meant to be. + timothyteakettle: + - bugfix: stops people using Message All on PDAs when their cartridge doesn't allow + it +2021-02-21: + Hatterhat: + - balance: Anomaly announcements and brand intelligence now always announce instead + of having some ham-fisted chance of being a command report. + IronEleven: + - balance: Raises Space Vine Population Requirement from 10 to 20 + MrJWhit: + - tweak: Removes an unnecessary % on the seed extractor. + timothyteakettle: + - bugfix: the query for checking mentors now gets properly deleted + - rscadd: vampires no longer burn in the chapel if they signed up as the chaplain +2021-02-22: + Putnam3145: + - rscadd: (Hexa)crocin + - rscadd: (Hexa)camphor + - rscadd: Nymphomaniac quirk + - admin: All climaxes and arousals are now logged, as well as genital exposure. + SandPoot: + - rscadd: Cyborg tablets and it's special app for self-management. + - bugfix: In the case of a doomsday device being created outside of an AI it will + delete itself. + - imageadd: Some sprites for it have been added and the borg's hud light toggles + been changed to only on-off (made by yours truly) + - refactor: A lot of borg code was changed + - refactor: Tools no longer use istype checks and actually check for their behavior. + Vynzill: + - rscadd: cursed rice hat that's hard to find and obtain, along with a couple other + hats + - rscadd: a replacement toy gun for donksoft lmg + - rscadd: gorillas to the jungle gateway, friendly, even when attacked. + - bugfix: couple mapping errors I noticed, most importantly a missing window in + the chapel. + - balance: shotgun and donksoft lmg removed, captain coat nerfed armor values. + - balance: leaper healthpool from 450 to 550 hopefully making it more of a struggle, + and gives it a name. + - tweak: leaper pit is more wider. The hidden room south is now more obvious to + find + dzahlus: + - rscadd: Added pain emote to getting wounded + - soundadd: added a new pain emote sounds +2021-02-23: + keronshb: + - rscadd: Hyperblade to uplink with poplock + - balance: Removes combination of two Dragon Tooth Swords while keeping it for regular + eutactics. + timothyteakettle: + - bugfix: banning panel prioritises mobs with clients now when trying to find them + if they're in the game +2021-02-24: + SandPoot: + - bugfix: Regular crowbars no longer open powered airlocks. + silicons: + - balance: xeno cube makes hostile xenos now, and drops a sentinel instead of a + drone. +2021-02-25: + DeltaFire15: + - bugfix: Traitor / Ling objective amount should now be correct again. +2021-02-26: + DeltaFire15: + - code_imp: All machine-frame based tool-use actions now have state-checking callbacks. +2021-02-27: + Hatterhat: + - balance: Lingfists (trait_mauler) now deal no stam damage and lost their 15(!!!) + armor penetration. + Putnam3145: + - tweak: Tablets now protect their contents from rads. + TheObserver-sys: + - rscadd: Chems that should have been usable are now usable, try some cryoxadone + on a plant today!!! + kappa-sama: + - tweak: cards and card binders are now small-class items + keronshb: + - balance: 16 > 10 unlock cost for stronger abilities + - balance: Made nearly all other abilities for free. + kiwedespars: + - balance: reverted the pr that absolutely gutted pugilism and made it worse than + base unarmed, also gives it a second long stagger + - balance: removed the ability to parry while horizontal, because that's dumb and + makes it easy to just time the parries right. + silicons: + - bugfix: chaplain arrythmic knives can no longer be abused for infinite speed. +2021-02-28: + Putnam3145: + - bugfix: Polychromic windbreaker's alt-click message is now coherent + - code_imp: Toggleable suits now have an on_toggle proc to be overridden. + R3dtail: + - tweak: doubled max belly name length and quadrupled belly description length + SandPoot: + - tweak: Body rejuvenation surgery will loop until the patient is completely healed. + dzahlus: + - bugfix: fixes toxinlovers dying from heretic stuff that should heal them instead +2021-03-01: + SmArtKar: + - bugfix: Fixes decks breaking your screen + - bugfix: Fixes binders not saving cards + - bugfix: Fixes binders not saving multiple cards of the same type + Vynzill: + - bugfix: cursed rice hat right in front of the jungle gateway's entrance is now + removed from this dimensional plane +2021-03-02: + LetterN: + - bugfix: 'colorpainter: let''s not dispense null' + SandPoot: + - bugfix: Changelings will actually become the person they want to be when using + "human form" ability(after having used last resort). +2021-03-03: + MarinaGryphon: + - bugfix: The AOOC mute pref is now properly respected. + - bugfix: Muting adminhelp sounds no longer mutes AOOC. + Putnam3145: + - config: pAIs now have a policy config + - rscadd: '"Supermatter surge" event, which might cause problems if the supermatter + is not sufficiently cooled (i.e. the setup is messed up in some way)' + - rscdel: Fusion can no longer be done in open air. + - rscdel: Valentine's day event no longer gives everyone a valentine's antag. + SandPoot: + - bugfix: Legions should now pass their type to the person they infect (if valid). + dzahlus: + - rscadd: Added new subtype to lesser ash drake balanced around player control + - balance: rebalanced dragon transformation to a 1 minute cooldown as well as using + the new subtype of megafauna + qweq12yt: + - bugfix: fixed infectious zombies not being able to attack if host was pacifist + - rscadd: adds a way for species to have blacklisted quirks, the removal, and restoration + of said quirks upon species changes + - bugfix: Now pacifists won't be able to use flamethrowers + - bugfix: Kinetic Accelerator now properly reloads a charge to it's chamber instead + of nulling the variable forever + - bugfix: Now pacifists won't be able to use Kinetic Accelerators if a non-pacifist + shoots it first +2021-03-04: + LetterN: + - code_imp: removes bsql +2021-03-05: + Putnam3145: + - tweak: Lowered ash storm volume + - tweak: Minesweeper can no longer be made to lag the server on purpose + keronshb: + - tweak: Prevents heat from going through reinforced plasma glass. +2021-03-07: + Hatterhat: + - rscadd: You can now reskin your improvised shotguns. + - rscadd: The spontaneous brain trauma event now announces to ghosts whoever got + funnied upon. + - imageadd: Ports EikoBiko's cat tail sprite. + Putnam3145: + - bugfix: nitryl now consumes oxygen/nitrogen instead of generating them + - balance: Hyper-nob and nitryl are easier to make. + dzahlus: + - rscadd: Added taser microbattery for MWS-01 + - tweak: tweaked MWS-01 beacondrop to have more batteries + - balance: rebalanced MWS-01 disabler battery to fire 10 shots + - soundadd: added unique sound to the MWS-01 + - spellcheck: fixed Modula Weapons System to "Modular Weapon System" + timothyteakettle: + - balance: exiting a bluespace jar through any means, hardstuns you for 5 seconds +2021-03-09: + LetterN: + - refactor: tg hardsync, mostly contains tgui +2021-03-10: + Hatterhat: + - rscadd: The femur breaker now actually breaks legs by applying a compound fracture. + Putnam3145: + - balance: uncapped TEG power, buffing high-temp TEGs +2021-03-11: + Putnam3145: + - rscadd: Supernova event + - refactor: Bloodsucker day/night cycle now processes on a subsystem instead of + using while and sleep (!!) + - rscadd: A chaos-weighting system for secret + Sneakyrat: + - bugfix: Fixes precise insertion ui(when it asks you how many sheets you want to + put in the mat container) so you can actually choose how much to put in and + it will still work if your stack is bigger than the remaining space + - bugfix: Fixes precise insertion not working for remote mat containers + - bugfix: Fixes Protolathe/mechfab insert material animations not working + - bugfix: Fixes the material insertion animation for protolathes occurring on the + circuit printer + - bugfix: Fixes the material insertion animation not actually playing when it was + started + - bugfix: Fixes the material insertion animation for iron not being made + - bugfix: Fixes the material insertion animation sometimes not picking the right + material for sheets that have multiple different materials in them + necromanceranne: + - bugfix: Properly segments all the augment sprites so that they correctly display. + - bugfix: Properly labels the augment limb icon sprites. + - bugfix: Fixes going over the defined limit of roundstart prosthetics +2021-03-12: + R3dtail: + - spellcheck: Adds Periods and moves some words around. +2021-03-13: + Hatterhat: + - spellcheck: BRPEDs don't examine as "not just a BRPED painted orange". + silicons: + - balance: paper wizard is now sentience_boss. diff --git a/html/changelogs/AutoChangeLog-pr-13014.yml b/html/changelogs/AutoChangeLog-pr-13014.yml deleted file mode 100644 index 861e797669..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13014.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Hatterhat" -delete-after: True -changes: - - rscadd: "The wastes of Lavaland and the icy caverns of Snow Taxi rumble in unison." diff --git a/html/changelogs/AutoChangeLog-pr-13193.yml b/html/changelogs/AutoChangeLog-pr-13193.yml deleted file mode 100644 index 980a9366d8..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13193.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "silicons" -delete-after: True -changes: - - tweak: "medium screens are better now" diff --git a/html/changelogs/AutoChangeLog-pr-13233.yml b/html/changelogs/AutoChangeLog-pr-13233.yml deleted file mode 100644 index 4366abec64..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13233.yml +++ /dev/null @@ -1,6 +0,0 @@ -author: "Hatterhat" -delete-after: True -changes: - - rscadd: "Changeling bone gauntlets! They punch the shit out of people really good." - - tweak: "Guerilla gloves and gorilla gloves inherit the strip modifiers of their predecessors, because apparently they had those." - - balance: "Pugilists now always hit the targeted limb and never miss." diff --git a/html/changelogs/AutoChangeLog-pr-13252.yml b/html/changelogs/AutoChangeLog-pr-13252.yml deleted file mode 100644 index 5de40bb80b..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13252.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "timothyteakettle" -delete-after: True -changes: - - tweak: "looking at loadout equips loadout items on your preview image instead of job items" diff --git a/html/changelogs/AutoChangeLog-pr-13346.yml b/html/changelogs/AutoChangeLog-pr-13346.yml deleted file mode 100644 index 2e3d4e3280..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13346.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Putnam3145" -delete-after: True -changes: - - refactor: "Dwarf speech is no longer absolutely paranoid about word replacement." diff --git a/html/changelogs/AutoChangeLog-pr-13349.yml b/html/changelogs/AutoChangeLog-pr-13349.yml deleted file mode 100644 index 2469b31ecd..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13349.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "LetterN" -delete-after: True -changes: - - code_imp: "Updates git and build tests." diff --git a/html/changelogs/AutoChangeLog-pr-13405.yml b/html/changelogs/AutoChangeLog-pr-13405.yml deleted file mode 100644 index 176e886b0d..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13405.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "silicons" -delete-after: True -changes: - - tweak: "the warp implant now actually warps you back 10 seconds. leaves a trail, though. now unlimited us." diff --git a/html/changelogs/AutoChangeLog-pr-13444.yml b/html/changelogs/AutoChangeLog-pr-13444.yml deleted file mode 100644 index d24babda2e..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13444.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "timothyteakettle" -delete-after: True -changes: - - tweak: "you can now get a family heirlooms based off your species instead of job" diff --git a/html/changelogs/AutoChangeLog-pr-13461.yml b/html/changelogs/AutoChangeLog-pr-13461.yml deleted file mode 100644 index e2397a7aa0..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13461.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Detective-Google" -delete-after: True -changes: - - rscadd: "arcade carpet" diff --git a/html/changelogs/AutoChangeLog-pr-13472.yml b/html/changelogs/AutoChangeLog-pr-13472.yml deleted file mode 100644 index 8e8d043de6..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13472.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "timothyteakettle" -delete-after: True -changes: - - rscadd: "roundstart slimes can turn into puddles now" diff --git a/html/changelogs/AutoChangeLog-pr-13473.yml b/html/changelogs/AutoChangeLog-pr-13473.yml deleted file mode 100644 index aebdfbb3cb..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13473.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Tupinambis" -delete-after: True -changes: - - rscadd: "Arachnids (spider people) with limited night vision, flash vulnerability, and webbing." diff --git a/html/changelogs/AutoChangeLog-pr-13479.yml b/html/changelogs/AutoChangeLog-pr-13479.yml deleted file mode 100644 index fcfe65a47e..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13479.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "LetterN" -delete-after: True -changes: - - bugfix: "minimap text" - - code_imp: "ports cinematic upgrades" diff --git a/html/changelogs/AutoChangeLog-pr-13481.yml b/html/changelogs/AutoChangeLog-pr-13481.yml deleted file mode 100644 index f6049c552b..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13481.yml +++ /dev/null @@ -1,6 +0,0 @@ -author: "DeltaFire15" -delete-after: True -changes: - - bugfix: "the blacksmithing skill now works properly" - - tweak: "Anvils cannot be interacted with with hammers whilst they are already being used" - - tweak: "If someone has no gloves when interacting with heated ingots, they no longer ignore their effects." diff --git a/html/changelogs/AutoChangeLog-pr-13483.yml b/html/changelogs/AutoChangeLog-pr-13483.yml deleted file mode 100644 index 1db9304ffb..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13483.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "DeltaFire15" -delete-after: True -changes: - - bugfix: "The Revenant self-revive ability is no longer broken." diff --git a/html/changelogs/AutoChangeLog-pr-13487.yml b/html/changelogs/AutoChangeLog-pr-13487.yml deleted file mode 100644 index 4b0f92adfb..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13487.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "DeltaFire15" -delete-after: True -changes: - - bugfix: "People installing KA modkits in miner borgs is no longer broken." diff --git a/html/changelogs/AutoChangeLog-pr-13496.yml b/html/changelogs/AutoChangeLog-pr-13496.yml deleted file mode 100644 index 30308efaff..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13496.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Detective-Google" -delete-after: True -changes: - - rscadd: "explosions now get broadcasted to deadchat." diff --git a/html/changelogs/AutoChangeLog-pr-13497.yml b/html/changelogs/AutoChangeLog-pr-13497.yml deleted file mode 100644 index c5f569bad3..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13497.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "Zandario" -delete-after: True -changes: - - code_imp: "Added some framework for future species expansions, including clothing refitting." - - refactor: "Made majority of the relevant Species IDs and Categories pre-defined, also for easier expansion and use." diff --git a/html/changelogs/AutoChangeLog-pr-13498.yml b/html/changelogs/AutoChangeLog-pr-13498.yml deleted file mode 100644 index f956ee4b07..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13498.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "Bhijn" -delete-after: True -changes: - - tweak: "Changeling loudness is now determined as an average of all their abilities, rather than the sum" - - tweak: "To compensate for this, blood tests now require a loudness value of 1 or higher to detect ling blood. Additionally, blood test explosions are now triggered only when the loudness value is higher than 2." diff --git a/html/changelogs/AutoChangeLog-pr-13499.yml b/html/changelogs/AutoChangeLog-pr-13499.yml deleted file mode 100644 index 804fa9fc34..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13499.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Putnam3145" -delete-after: True -changes: - - bugfix: "Toilet loot spawners don't lag the server on server start with forced hard dels." diff --git a/html/changelogs/AutoChangeLog-pr-13500.yml b/html/changelogs/AutoChangeLog-pr-13500.yml deleted file mode 100644 index 7ba1eb64f3..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13500.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "LetterN" -delete-after: True -changes: - - rscadd: "2 more ways to get up from z1" - - tweak: "tweaked the z2 garden to be less blank" diff --git a/html/changelogs/AutoChangeLog-pr-13501.yml b/html/changelogs/AutoChangeLog-pr-13501.yml deleted file mode 100644 index dcdd868e37..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13501.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Putnam3145" -delete-after: True -changes: - - bugfix: "Holofirelocks work now." diff --git a/html/changelogs/AutoChangeLog-pr-13503.yml b/html/changelogs/AutoChangeLog-pr-13503.yml deleted file mode 100644 index c68eaf44f0..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13503.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "Hatterhat" -delete-after: True -changes: - - rscadd: "Survival daggers! A slightly more expensive survival knife that comes with a brighter flashlight. On the blade." - - tweak: "Luxury pod capsules look different from normal capsules." diff --git a/html/changelogs/AutoChangeLog-pr-13504.yml b/html/changelogs/AutoChangeLog-pr-13504.yml deleted file mode 100644 index 7f59b120b6..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13504.yml +++ /dev/null @@ -1,6 +0,0 @@ -author: "zeroisthebiggay" -delete-after: True -changes: - - rscadd: "gravitokinetic stands from tg" - - balance: "buffs stands overall" - - bugfix: "protector stands no longer become tposing invisible apes sometimes" diff --git a/html/changelogs/AutoChangeLog-pr-13509.yml b/html/changelogs/AutoChangeLog-pr-13509.yml deleted file mode 100644 index e666a6b2a4..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13509.yml +++ /dev/null @@ -1,6 +0,0 @@ -author: "EmeraldSundisk" -delete-after: True -changes: - - balance: "The \"Skelter ruin\" now has stechkins as opposed to M1911s" - - tweak: "Skelter's decorative bullet casings replaced to factor in the change in caliber" - - rscadd: "Skelter now has a combat knife and fluff note" diff --git a/html/changelogs/AutoChangeLog-pr-13511.yml b/html/changelogs/AutoChangeLog-pr-13511.yml deleted file mode 100644 index e819d8752d..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13511.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Hatterhat" -delete-after: True -changes: - - imageadd: "The Syndicate appear to be issuing new revolver variants." diff --git a/html/changelogs/AutoChangeLog-pr-13512.yml b/html/changelogs/AutoChangeLog-pr-13512.yml deleted file mode 100644 index f96f13a156..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13512.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Hatterhat" -delete-after: True -changes: - - bugfix: "Cryogenics now screams on common again when your fuckbuddy heads out." diff --git a/html/changelogs/AutoChangeLog-pr-13513.yml b/html/changelogs/AutoChangeLog-pr-13513.yml deleted file mode 100644 index 0cff979e8c..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13513.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "timothyteakettle" -delete-after: True -changes: - - rscadd: "you can now pick your eye sprites from customization" diff --git a/html/changelogs/AutoChangeLog-pr-13514.yml b/html/changelogs/AutoChangeLog-pr-13514.yml deleted file mode 100644 index bd1605fb96..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13514.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Thalpy" -delete-after: True -changes: - - bugfix: "fixes some bugs in jacqs code from edits to the codebase" diff --git a/html/changelogs/AutoChangeLog-pr-13516.yml b/html/changelogs/AutoChangeLog-pr-13516.yml deleted file mode 100644 index 30e22f4f48..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13516.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "yorii" -delete-after: True -changes: - - bugfix: "fixed botany rounding error that caused grass and other plants to misbehave" diff --git a/html/changelogs/AutoChangeLog-pr-13517.yml b/html/changelogs/AutoChangeLog-pr-13517.yml deleted file mode 100644 index 8f29517058..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13517.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "silicons" -delete-after: True -changes: - - bugfix: "simple mobs are now immune to radioactive contamination" diff --git a/html/changelogs/AutoChangeLog-pr-13518.yml b/html/changelogs/AutoChangeLog-pr-13518.yml deleted file mode 100644 index 565754f48b..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13518.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "TheObserver-sys" -delete-after: True -changes: - - bugfix: "Drake? Where's the dead fairygrass sprite?" diff --git a/html/changelogs/AutoChangeLog-pr-13519.yml b/html/changelogs/AutoChangeLog-pr-13519.yml deleted file mode 100644 index 0c4978a7d8..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13519.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Detective-Google" -delete-after: True -changes: - - rscadd: "Lick radial" diff --git a/html/changelogs/AutoChangeLog-pr-13521.yml b/html/changelogs/AutoChangeLog-pr-13521.yml deleted file mode 100644 index 77aca8c363..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13521.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "DeltaFire15" -delete-after: True -changes: - - balance: "zeolites are now actual fermichems instead of being incredibly easy to make." - - bugfix: "Using syringes / droppers on chem heaters with beakers in them works again." diff --git a/html/changelogs/AutoChangeLog-pr-13523.yml b/html/changelogs/AutoChangeLog-pr-13523.yml deleted file mode 100644 index ac2019bb88..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13523.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Detective-Google" -delete-after: True -changes: - - bugfix: "arcade carpets now actually work" diff --git a/html/changelogs/AutoChangeLog-pr-13525.yml b/html/changelogs/AutoChangeLog-pr-13525.yml deleted file mode 100644 index eb441f04c3..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13525.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Detective-Google" -delete-after: True -changes: - - bugfix: "Hilbert's jukebox works" diff --git a/html/changelogs/AutoChangeLog-pr-13527.yml b/html/changelogs/AutoChangeLog-pr-13527.yml deleted file mode 100644 index 7e65116e83..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13527.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "ArchieBeepBoop" -delete-after: True -changes: - - rscadd: "Upgraded Advanced RTG Machine Preset" diff --git a/html/changelogs/AutoChangeLog-pr-13528.yml b/html/changelogs/AutoChangeLog-pr-13528.yml deleted file mode 100644 index b7bd0d4618..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13528.yml +++ /dev/null @@ -1,11 +0,0 @@ -author: "DeltaFire15" -delete-after: True -changes: - - balance: "Robotic Limbs now each have their own damage threshhold values" - - balance: "Robotic Limb damage threshholds are now seperated into threshhold itself and mindamage when passed -balance; Hybrid limbs can now be injected with hypos, but not sprayed (Still not healed by chems)" - - tweak: "Brain surgery has been tweaked back to allowing robotic limbs, blacklisting IPC brains instead." - - tweak: "Robot brain surgery can now be used on organic heads, if there is a IPC brain in them somehow." - - tweak: "The robot limb heal surgery can now be used even if the target's torso is not robotic, as long as they have robotic limbs" - - refactor: "BODYPART_ROBOTIC / BODYPART_ORGANIC checks replaced with helper-procs whereever possible." - - code_imp: "Added a BODYPART_HYBRID define for robotic bodyparts that behave organic in some regards." diff --git a/html/changelogs/AutoChangeLog-pr-13529.yml b/html/changelogs/AutoChangeLog-pr-13529.yml deleted file mode 100644 index 54f775b5c8..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13529.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Putnam3145" -delete-after: True -changes: - - tweak: "Planetary atmos no longer does superconduction." diff --git a/html/changelogs/AutoChangeLog-pr-13530.yml b/html/changelogs/AutoChangeLog-pr-13530.yml deleted file mode 100644 index b99ed1c545..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13530.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "silicons" -delete-after: True -changes: - - rscadd: "Clowns now have unpredictable effects on supermatter crystals when dusting from contact." diff --git a/html/changelogs/AutoChangeLog-pr-13531.yml b/html/changelogs/AutoChangeLog-pr-13531.yml deleted file mode 100644 index 3c138ef98d..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13531.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "DeltaFire15" -delete-after: True -changes: - - bugfix: "A certain lizard (totally not me) being stupid is no longer going to break regenerate_bodyparts" diff --git a/html/changelogs/AutoChangeLog-pr-13533.yml b/html/changelogs/AutoChangeLog-pr-13533.yml deleted file mode 100644 index 1ba14c689c..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13533.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "shellspeed1" -delete-after: True -changes: - - rscadd: "A new recipe for a spicy has been given to us by a strange business man." diff --git a/html/changelogs/AutoChangeLog-pr-13535.yml b/html/changelogs/AutoChangeLog-pr-13535.yml deleted file mode 100644 index b020587eb8..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13535.yml +++ /dev/null @@ -1,7 +0,0 @@ -author: "Hatterhat" -delete-after: True -changes: - - rscadd: "Energy sabre reskin for the energy sword - access via alt-click." - - bugfix: "Alt-click reskins are fixed." - - tweak: "Defibrillators and their many, many overlays were moved to another .dmi." - - tweak: "You can now change the color of an energy sword via multitool. Not deswords. Yet." diff --git a/html/changelogs/AutoChangeLog-pr-13539.yml b/html/changelogs/AutoChangeLog-pr-13539.yml deleted file mode 100644 index a1f3df4fd0..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13539.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "timothyteakettle" -delete-after: True -changes: - - rscdel: "removes two debug messages left in from my prior eye customization pr" diff --git a/html/changelogs/AutoChangeLog-pr-13540.yml b/html/changelogs/AutoChangeLog-pr-13540.yml deleted file mode 100644 index 0d7d9b8d2c..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13540.yml +++ /dev/null @@ -1,6 +0,0 @@ -author: "ArcaneMusic, The0bserver-sys" -delete-after: True -changes: - - rscadd: "New from Hydrowear LLC: The Botanical Belt! This handy yellow belt lets you hold most of your botany gear, and a few beakers for reduced bag and floor clutter!" - - tweak: "Gives Hydrotrays plumbing pipes automatically, allowing you to make a self sustaining tray via plumbing." - - tweak: "Gives Service access to Bluespace Beakers, at last, gives Cargo, Science, and Medical the ability to construct reinforced plungers for use on lavaland." diff --git a/html/changelogs/AutoChangeLog-pr-13543.yml b/html/changelogs/AutoChangeLog-pr-13543.yml deleted file mode 100644 index be2cb215c9..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13543.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "zeroisthebiggay" -delete-after: True -changes: - - rscadd: "The legion megafauna has been reworked. The fight should now be both slightly harder and faster." - - balance: "You can no longer cheese the colossus by being a sand golem and simply being immune." diff --git a/html/changelogs/AutoChangeLog-pr-13544.yml b/html/changelogs/AutoChangeLog-pr-13544.yml deleted file mode 100644 index 61cf99090d..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13544.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "DeltaFire15" -delete-after: True -changes: - - bugfix: "Portals no longer runtime because of incorrect args." diff --git a/html/changelogs/AutoChangeLog-pr-13547.yml b/html/changelogs/AutoChangeLog-pr-13547.yml deleted file mode 100644 index 0355a871d1..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13547.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "zeroisthebiggay" -delete-after: True -changes: - - rscadd: "secsheath for your cool stunsword at your local security vendor. you gotta hack it first though." diff --git a/html/changelogs/AutoChangeLog-pr-13549.yml b/html/changelogs/AutoChangeLog-pr-13549.yml deleted file mode 100644 index 5d46fbcb6c..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13549.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "BlueWildrose" -delete-after: True -changes: - - bugfix: "Fixes cloning computer UI not updating when pressing certain buttons - also adds extra check for names to update a message" diff --git a/html/changelogs/AutoChangeLog-pr-13552.yml b/html/changelogs/AutoChangeLog-pr-13552.yml deleted file mode 100644 index 1504d1d3a0..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13552.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Putnam3145" -delete-after: True -changes: - - bugfix: "Multi-surgery unit test no longer fails at random." diff --git a/html/changelogs/AutoChangeLog-pr-13553.yml b/html/changelogs/AutoChangeLog-pr-13553.yml deleted file mode 100644 index 2759fa2754..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13553.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "timothyteakettle" -delete-after: True -changes: - - bugfix: "eye sprites should look normal once more" diff --git a/html/changelogs/AutoChangeLog-pr-13554.yml b/html/changelogs/AutoChangeLog-pr-13554.yml deleted file mode 100644 index 3a14adff37..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13554.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Vynzill" -delete-after: True -changes: - - bugfix: "fixes high luminosity eyes" diff --git a/html/changelogs/AutoChangeLog-pr-13558.yml b/html/changelogs/AutoChangeLog-pr-13558.yml deleted file mode 100644 index 2a5b2dc576..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13558.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "Zandario" -delete-after: True -changes: - - bugfix: "lum slime sprites work again" - - code_imp: "Slapped the Species Defines where relevant" diff --git a/html/changelogs/AutoChangeLog-pr-13561.yml b/html/changelogs/AutoChangeLog-pr-13561.yml deleted file mode 100644 index 097336bc3b..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13561.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "DeltaFire15" -delete-after: True -changes: - - bugfix: "Your target cryoing will no longer give you a free greentext." diff --git a/html/changelogs/AutoChangeLog-pr-13562.yml b/html/changelogs/AutoChangeLog-pr-13562.yml deleted file mode 100644 index 7fabc05c7b..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13562.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "DeltaFire15" -delete-after: True -changes: - - bugfix: "Vampire shapeshifting should now behave as intended" diff --git a/html/changelogs/AutoChangeLog-pr-13563.yml b/html/changelogs/AutoChangeLog-pr-13563.yml deleted file mode 100644 index c4c9b68c39..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13563.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "timothyteakettle" -delete-after: True -changes: - - tweak: "cloning now correctly copies your blood colour, body sprite type and eye type" diff --git a/html/changelogs/AutoChangeLog-pr-13566.yml b/html/changelogs/AutoChangeLog-pr-13566.yml deleted file mode 100644 index 14ee8755a9..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13566.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "zeroisthebiggay" -delete-after: True -changes: - - bugfix: "legion now drops chests" diff --git a/html/changelogs/AutoChangeLog-pr-13567.yml b/html/changelogs/AutoChangeLog-pr-13567.yml deleted file mode 100644 index d51db8e3b8..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13567.yml +++ /dev/null @@ -1,8 +0,0 @@ -author: "timothyteakettle" -delete-after: True -changes: - - tweak: "changeling stings retract upon turning into a slime puddle" - - tweak: "you cannot transform into a slime puddle with a no drop item in your hands" - - tweak: "slime puddles are now transparent and their colour looks more natural in comparison to the user" - - tweak: "slime puddles are now even slower" - - tweak: "slime puddles now get no protection from worn clothing" diff --git a/html/changelogs/AutoChangeLog-pr-13570.yml b/html/changelogs/AutoChangeLog-pr-13570.yml deleted file mode 100644 index 6a4fb6af66..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13570.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Putnam3145" -delete-after: True -changes: - - bugfix: "Fixed a runtime in every healing nanite program." diff --git a/html/changelogs/AutoChangeLog-pr-13571.yml b/html/changelogs/AutoChangeLog-pr-13571.yml deleted file mode 100644 index 864967dffc..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13571.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "MrJWhit" -delete-after: True -changes: - - bugfix: "Code improvement on ventcrawling" diff --git a/html/changelogs/AutoChangeLog-pr-13573.yml b/html/changelogs/AutoChangeLog-pr-13573.yml deleted file mode 100644 index 9bac0ce44d..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13573.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Putnam3145" -delete-after: True -changes: - - bugfix: "removed a unit test causing master to fail" diff --git a/html/changelogs/AutoChangeLog-pr-13575.yml b/html/changelogs/AutoChangeLog-pr-13575.yml deleted file mode 100644 index 164581b11f..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13575.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "BlueWildrose" -delete-after: True -changes: - - bugfix: "Fixed the subtle hotkey being weird with its input prompts." - - rscadd: "Adds a subtler anti-ghost hotkey. Default key is 6." diff --git a/html/changelogs/AutoChangeLog-pr-13576.yml b/html/changelogs/AutoChangeLog-pr-13576.yml deleted file mode 100644 index 9443b13dcb..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13576.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "Sonic121x" -delete-after: True -changes: - - rscadd: "digi sprite uniform" - - bugfix: "digi leg suit" diff --git a/html/changelogs/AutoChangeLog-pr-13577.yml b/html/changelogs/AutoChangeLog-pr-13577.yml deleted file mode 100644 index 7463a3ad39..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13577.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Putnam3145" -delete-after: True -changes: - - bugfix: "Chemical stuff now displays fermichem stuff properly" diff --git a/html/changelogs/AutoChangeLog-pr-13581.yml b/html/changelogs/AutoChangeLog-pr-13581.yml deleted file mode 100644 index 42db54450b..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13581.yml +++ /dev/null @@ -1,6 +0,0 @@ -author: "Ghommie, porting PRs by MMMiracles and pireamaineach, credits to BlueWildrose too." -delete-after: True -changes: - - rscadd: "You can now draw on plasmaman helmets with a crayon to turn their frown upside-down." - - balance: "Plasmaman helmets no longer hide your identity when worn by themselves." - - balance: "Plasmaman helmets now have welding visors, which can't stack with their torches in the helmet and are visible." diff --git a/html/changelogs/AutoChangeLog-pr-13582.yml b/html/changelogs/AutoChangeLog-pr-13582.yml deleted file mode 100644 index 78f8be1cd0..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13582.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "timothyteakettle" -delete-after: True -changes: - - bugfix: "a tonne of fixes to colourisation of parts, too many to name, including some sprite fixes" - - rscadd: "things now have their own individual primary/(secondary)/(tertiary) colours as required, and these can be modified by you" diff --git a/html/changelogs/AutoChangeLog-pr-13585.yml b/html/changelogs/AutoChangeLog-pr-13585.yml deleted file mode 100644 index 0369e0827f..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13585.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "BlueWildrose" -delete-after: True -changes: - - bugfix: "Fixed species-specific drinks not giving a mood boost if you are that species." diff --git a/html/changelogs/AutoChangeLog-pr-13586.yml b/html/changelogs/AutoChangeLog-pr-13586.yml deleted file mode 100644 index 5f61b1dc54..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13586.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "KeRSedChaplain" -delete-after: True -changes: - - imageadd: "Resprited the brass claw" diff --git a/html/changelogs/AutoChangeLog-pr-13587.yml b/html/changelogs/AutoChangeLog-pr-13587.yml deleted file mode 100644 index ea3f4c5efa..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13587.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Detective-Google" -delete-after: True -changes: - - tweak: "the snow taxi is no longer the slow taxi" diff --git a/html/changelogs/AutoChangeLog-pr-13588.yml b/html/changelogs/AutoChangeLog-pr-13588.yml deleted file mode 100644 index 7ab9d67bab..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13588.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "Delams-The-SM" -delete-after: True -changes: - - rscadd: "Added 3 new emotes *hiss *purr *meow" - - soundadd: "ported sounds from Citadel RP for *purr and *meow" diff --git a/html/changelogs/AutoChangeLog-pr-13589.yml b/html/changelogs/AutoChangeLog-pr-13589.yml deleted file mode 100644 index 877012914e..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13589.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "timothyteakettle" -delete-after: True -changes: - - tweak: "autosurgeons from travelling trader rewards now only have one use" diff --git a/html/changelogs/AutoChangeLog-pr-13592.yml b/html/changelogs/AutoChangeLog-pr-13592.yml deleted file mode 100644 index 76e12e8c89..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13592.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Putnam3145" -delete-after: True -changes: - - rscadd: "Stat panel UI for ranked choice votes" diff --git a/html/changelogs/AutoChangeLog-pr-13593.yml b/html/changelogs/AutoChangeLog-pr-13593.yml deleted file mode 100644 index 08310f6013..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13593.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Putnam3145" -delete-after: True -changes: - - code_imp: "Vote system #defines are now strings" diff --git a/html/changelogs/AutoChangeLog-pr-13598.yml b/html/changelogs/AutoChangeLog-pr-13598.yml deleted file mode 100644 index 7bef81fa1a..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13598.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Arturlang" -delete-after: True -changes: - - bugfix: "Fixes hijack implant APC UI, again" diff --git a/html/changelogs/AutoChangeLog-pr-13599.yml b/html/changelogs/AutoChangeLog-pr-13599.yml deleted file mode 100644 index 3deea2b2be..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13599.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Delams-The-SM" -delete-after: True -changes: - - bugfix: "fixed randomization of colors for things like mulligan and Stabilized green slime extract for matrixed body parts" diff --git a/html/changelogs/AutoChangeLog-pr-13601.yml b/html/changelogs/AutoChangeLog-pr-13601.yml deleted file mode 100644 index 0cfb64b48b..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13601.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "timothyteakettle" -delete-after: True -changes: - - tweak: "antag items are now of critical importance and wont fail to be placed on the character" diff --git a/html/changelogs/AutoChangeLog-pr-13602.yml b/html/changelogs/AutoChangeLog-pr-13602.yml deleted file mode 100644 index 24185b1792..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13602.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "Putnam3145" -delete-after: True -changes: - - rscadd: "A fallback for dynamic antag rolling that allows for it to just try between traitor, blood brothers, heretics, changeling, bloodsucker and devil until there are enough roundstart antags. This can also happen randomly anyway. Blood brothers and devil are disabled for now, but the code is there to enable them." - - rscadd: "A new storyteller, \"Grab Bag\", that forces the above round type." diff --git a/html/changelogs/AutoChangeLog-pr-13603.yml b/html/changelogs/AutoChangeLog-pr-13603.yml deleted file mode 100644 index 4fcff30da0..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13603.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "kappa-sama" -delete-after: True -changes: - - tweak: "ishotgun crafting recipe no longer requires plasteel and is slightly more convenient" - - balance: "ishotgun does 45 damage now instead of 40.5" diff --git a/html/changelogs/AutoChangeLog-pr-13604.yml b/html/changelogs/AutoChangeLog-pr-13604.yml deleted file mode 100644 index 2ac141215b..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13604.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "Trilbyspaceclone" -delete-after: True -changes: - - balance: "Brass welders are 50% faster at refueling" - - code_imp: "redoes self fueling welders in the code to be less speggie" diff --git a/html/changelogs/AutoChangeLog-pr-13605.yml b/html/changelogs/AutoChangeLog-pr-13605.yml deleted file mode 100644 index 707de17870..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13605.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Trilbyspaceclone" -delete-after: True -changes: - - rscadd: "the corporate unifoms can now be gotton in the clothing mate vender" diff --git a/html/changelogs/AutoChangeLog-pr-13606.yml b/html/changelogs/AutoChangeLog-pr-13606.yml deleted file mode 100644 index 568f8cf7e6..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13606.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "Putnam3145" -delete-after: True -changes: - - bugfix: "adminhelping no longer removes entire admin tab" - - bugfix: "end of round no longer removes entire admin tab" diff --git a/html/changelogs/AutoChangeLog-pr-13607.yml b/html/changelogs/AutoChangeLog-pr-13607.yml deleted file mode 100644 index 0ed47b1d44..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13607.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "timothyteakettle" -delete-after: True -changes: - - rscadd: "polychromic maid outfit" diff --git a/html/changelogs/AutoChangeLog-pr-13608.yml b/html/changelogs/AutoChangeLog-pr-13608.yml deleted file mode 100644 index b7c0201cab..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13608.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "SpaceManiac" -delete-after: True -changes: - - bugfix: "Fixed the maphook" diff --git a/html/changelogs/AutoChangeLog-pr-13611.yml b/html/changelogs/AutoChangeLog-pr-13611.yml deleted file mode 100644 index d7e6e8f9a1..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13611.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "MrJWhit" -delete-after: True -changes: - - rscadd: "Adds a space loop to every map in toxins" diff --git a/html/changelogs/AutoChangeLog-pr-13612.yml b/html/changelogs/AutoChangeLog-pr-13612.yml deleted file mode 100644 index 9a67ec5aa3..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13612.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "MrJWhit" -delete-after: True -changes: - - rscadd: "Expanded space hermit base" diff --git a/html/changelogs/AutoChangeLog-pr-13616.yml b/html/changelogs/AutoChangeLog-pr-13616.yml deleted file mode 100644 index 3032d0331f..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13616.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Ghommie" -delete-after: True -changes: - - bugfix: "You can access the mime / clown mask skins radial menu once again." diff --git a/html/changelogs/AutoChangeLog-pr-13617.yml b/html/changelogs/AutoChangeLog-pr-13617.yml deleted file mode 100644 index 532882eecc..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13617.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "timothyteakettle" -delete-after: True -changes: - - rscadd: "loadout now has save slot support and colour choosing/saving for polychromic items" diff --git a/html/changelogs/AutoChangeLog-pr-13618.yml b/html/changelogs/AutoChangeLog-pr-13618.yml deleted file mode 100644 index b301c39e6b..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13618.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "Vynzill" -delete-after: True -changes: - - rscadd: "new gateway mission -mapadd: jungleresort map" diff --git a/html/changelogs/AutoChangeLog-pr-13626.yml b/html/changelogs/AutoChangeLog-pr-13626.yml deleted file mode 100644 index fa5331e005..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13626.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "zeroisthebiggay" -delete-after: True -changes: - - balance: "MEGAFAUNA DROPS ARE LAVAPROOF" diff --git a/html/changelogs/AutoChangeLog-pr-13627.yml b/html/changelogs/AutoChangeLog-pr-13627.yml deleted file mode 100644 index 087cbde3ae..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13627.yml +++ /dev/null @@ -1,8 +0,0 @@ -author: "Ghommie" -delete-after: True -changes: - - bugfix: "Dice bags no longer act like cardboard boxes." - - bugfix: "Abductors should be no longer mute." - - bugfix: "Item action buttons should now properly show the item current overlays, most times." - - bugfix: "The blackbox should now go into your hand slot when pried out, rather than tumbling on the ground everytime." - - tweak: "The Quick Equip hotkey is now usable by all living mobs (so long they have hands and equipment slots)" diff --git a/html/changelogs/AutoChangeLog-pr-13629.yml b/html/changelogs/AutoChangeLog-pr-13629.yml deleted file mode 100644 index 954f6972ec..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13629.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "silicons" -delete-after: True -changes: - - rscdel: "anomalies no longer spawn in walls" diff --git a/html/changelogs/AutoChangeLog-pr-13630.yml b/html/changelogs/AutoChangeLog-pr-13630.yml deleted file mode 100644 index f7e80aaf9e..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13630.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "timothyteakettle" -delete-after: True -changes: - - rscadd: "you can now customize your size from 90% to 130%, going below 100% makes you have 10 less max health" diff --git a/html/changelogs/AutoChangeLog-pr-13632.yml b/html/changelogs/AutoChangeLog-pr-13632.yml deleted file mode 100644 index bf478ec97c..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13632.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "timothyteakettle" -delete-after: True -changes: - - bugfix: "time for memory loss message to show up when being revived is now correctly 300 seconds, instead of 30" diff --git a/html/changelogs/AutoChangeLog-pr-13633.yml b/html/changelogs/AutoChangeLog-pr-13633.yml deleted file mode 100644 index d9246cfff6..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13633.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "ArchieBeepBoop" -delete-after: True -changes: - - bugfix: "Outlet Injector Mapping Asset Layer Fix" diff --git a/html/changelogs/AutoChangeLog-pr-13635.yml b/html/changelogs/AutoChangeLog-pr-13635.yml deleted file mode 100644 index 21730d4cf5..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13635.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Hatterhat" -delete-after: True -changes: - - balance: "The bone gauntlets should be slightly less murderously punchy on the fast punches mode." diff --git a/html/changelogs/AutoChangeLog-pr-13636.yml b/html/changelogs/AutoChangeLog-pr-13636.yml deleted file mode 100644 index 75124f759f..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13636.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "SiliconMain" -delete-after: True -changes: - - tweak: "Engi department has gas masks in loadout" diff --git a/html/changelogs/AutoChangeLog-pr-13639.yml b/html/changelogs/AutoChangeLog-pr-13639.yml deleted file mode 100644 index a511d0a909..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13639.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Trilbyspaceclone" -delete-after: True -changes: - - tweak: "ASP 9mm and M1911 can now have suppressers added" diff --git a/html/changelogs/AutoChangeLog-pr-13641.yml b/html/changelogs/AutoChangeLog-pr-13641.yml deleted file mode 100644 index b0c429da75..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13641.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "MrJWhit" -delete-after: True -changes: - - balance: "Nerf combat knife damage" diff --git a/html/changelogs/AutoChangeLog-pr-13644.yml b/html/changelogs/AutoChangeLog-pr-13644.yml deleted file mode 100644 index 71417788a7..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13644.yml +++ /dev/null @@ -1,8 +0,0 @@ -author: "DeltaFire15" -delete-after: True -changes: - - balance: "Biomechanical (hybrid) bodyparts now have access to wound-fixing surgeries." - - tweak: "A wound being fixed no longer just qdel()s surgeries connected to it." - - tweak: "Some robotic surgery steps are now a bit more clear." - - bugfix: "Organs no longer get fed to people after successfully being inserted into them." - - tweak: "Not completing the do_after of a surgery no longer causes you to attack the target with whatever you were holding." diff --git a/html/changelogs/AutoChangeLog-pr-13647.yml b/html/changelogs/AutoChangeLog-pr-13647.yml deleted file mode 100644 index b3211ee759..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13647.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "zeroisthebiggay" -delete-after: True -changes: - - bugfix: "jacqueline spawns on boxstation" diff --git a/html/changelogs/AutoChangeLog-pr-13648.yml b/html/changelogs/AutoChangeLog-pr-13648.yml deleted file mode 100644 index 4148230385..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13648.yml +++ /dev/null @@ -1,13 +0,0 @@ -author: "DeltaFire15" -delete-after: True -changes: - - bugfix: "The 'Your body is in a cloner' notification works again" - - bugfix: "Hijack implants should work properly again (or, at least better)" - - bugfix: "Liches are now good skeletons again instead of weak ones" - - bugfix: "The piratepad control cannot be destroyed again." - - bugfix: "Pirates have received new supplies of jetpacks instead of useless oxygen tanks" - - bugfix: "Ratvarian AIs are once again able to show their linked borgs Ratvar's light" - - bugfix: "Hijackers are once again unable to detonate borgs without being adjacent to the console" - - bugfix: "Automated annoucement systems and gulag ore consoles no longer waste emag charges" - - bugfix: "Automated announcement systems once again can be remote controlled by non-AIs with silicon access" - - bugfix: "APCs being hijacked multiple times at once is no longer possible, preventing some issues" diff --git a/html/changelogs/AutoChangeLog-pr-13653.yml b/html/changelogs/AutoChangeLog-pr-13653.yml deleted file mode 100644 index 2f920da353..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13653.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "MrJWhit" -delete-after: True -changes: - - tweak: "Minor fixes to kilo" diff --git a/html/changelogs/AutoChangeLog-pr-13654.yml b/html/changelogs/AutoChangeLog-pr-13654.yml deleted file mode 100644 index cb67c6e6be..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13654.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "ArchieBeepBoop" -delete-after: True -changes: - - bugfix: "Jacqueen and the Christmas tree should no longer spawn abstract things that can cause shittons of runtimes." diff --git a/html/changelogs/AutoChangeLog-pr-13655.yml b/html/changelogs/AutoChangeLog-pr-13655.yml deleted file mode 100644 index 6e48c84054..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13655.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "timothyteakettle" -delete-after: True -changes: - - bugfix: "fixes held items proccing crossed when passing someone" diff --git a/html/changelogs/AutoChangeLog-pr-13656.yml b/html/changelogs/AutoChangeLog-pr-13656.yml deleted file mode 100644 index 017f0d99c2..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13656.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "MrJWhit" -delete-after: True -changes: - - admin: "Canceling events gives more time to stop from 10 to 30" diff --git a/html/changelogs/AutoChangeLog-pr-13657.yml b/html/changelogs/AutoChangeLog-pr-13657.yml deleted file mode 100644 index 6f445f6b71..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13657.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "DeltaFire15" -delete-after: True -changes: - - bugfix: "The transmission sigil power drain works now" diff --git a/html/changelogs/AutoChangeLog-pr-13658.yml b/html/changelogs/AutoChangeLog-pr-13658.yml deleted file mode 100644 index 09b22d38ad..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13658.yml +++ /dev/null @@ -1,6 +0,0 @@ -author: "DeltaFire15" -delete-after: True -changes: - - bugfix: "Scripture no longer sometimes eats part of its invocation." - - balance: "APCs and silicons are now more susceptible to powerdrains (by the power_drain() proc, which is rare)" - - balance: "Void Volt has been modified from a chant to a singular pulse." diff --git a/html/changelogs/AutoChangeLog-pr-13659.yml b/html/changelogs/AutoChangeLog-pr-13659.yml deleted file mode 100644 index 85eee3b78d..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13659.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "timothyteakettle" -delete-after: True -changes: - - bugfix: "the load away mission verb won't crash the server now" diff --git a/html/changelogs/AutoChangeLog-pr-13661.yml b/html/changelogs/AutoChangeLog-pr-13661.yml deleted file mode 100644 index 81b5a735dc..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13661.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "timothyteakettle" -delete-after: True -changes: - - rscadd: "added in-game age verification as an alternative to access requests" diff --git a/html/changelogs/AutoChangeLog-pr-13662.yml b/html/changelogs/AutoChangeLog-pr-13662.yml deleted file mode 100644 index 6ac16937d8..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13662.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "silicons" -delete-after: True -changes: - - rscadd: "you can now be an angel using a magic mirror again" diff --git a/html/changelogs/AutoChangeLog-pr-13669.yml b/html/changelogs/AutoChangeLog-pr-13669.yml deleted file mode 100644 index c682500501..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13669.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Arturlang" -delete-after: True -changes: - - tweak: "Being sacrificed by the cult no longer removes all hope of rescue." diff --git a/html/changelogs/AutoChangeLog-pr-13670.yml b/html/changelogs/AutoChangeLog-pr-13670.yml deleted file mode 100644 index 594e459d5a..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13670.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "shellspeed1" -delete-after: True -changes: - - tweak: "Floor bots are now buildable with all toolboxes." diff --git a/html/changelogs/AutoChangeLog-pr-13671.yml b/html/changelogs/AutoChangeLog-pr-13671.yml deleted file mode 100644 index 855028d9ed..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13671.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "DeltaFire15" -delete-after: True -changes: - - code_imp: "Clockwork rites now support hiding specific rites from neutered servants." diff --git a/html/changelogs/AutoChangeLog-pr-13673.yml b/html/changelogs/AutoChangeLog-pr-13673.yml deleted file mode 100644 index 40b45d80f4..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13673.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Arturlang" -delete-after: True -changes: - - bugfix: "Makes construct mind returning more robust" diff --git a/html/changelogs/AutoChangeLog-pr-13674.yml b/html/changelogs/AutoChangeLog-pr-13674.yml deleted file mode 100644 index ccbd96aeb4..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13674.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "DeltaFire15" -delete-after: True -changes: - - bugfix: "The borg VTEC ability now actually gets removed when the upgrade is removed." diff --git a/html/changelogs/AutoChangeLog-pr-13675.yml b/html/changelogs/AutoChangeLog-pr-13675.yml deleted file mode 100644 index d46d4490d3..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13675.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "DeltaFire15" -delete-after: True -changes: - - bugfix: "Sleeper UI interactiveness now behaves correctly." diff --git a/html/changelogs/AutoChangeLog-pr-13676.yml b/html/changelogs/AutoChangeLog-pr-13676.yml deleted file mode 100644 index d584af38dc..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13676.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "DeltaFire15" -delete-after: True -changes: - - bugfix: "Fixes a minor incorrectness in ratvarian borg slabs (ratvar_act -> ui_act)" diff --git a/html/changelogs/AutoChangeLog-pr-13678.yml b/html/changelogs/AutoChangeLog-pr-13678.yml deleted file mode 100644 index 261ecc3475..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13678.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Arturlang" -delete-after: True -changes: - - code_imp: "Comments out spaceman dmm do not sleeps for mob/proc/CommonClickOn, atom/proc/attack_hand, datum/proc/keyLoop and mob/living/proc/Life" diff --git a/html/changelogs/AutoChangeLog-pr-13679.yml b/html/changelogs/AutoChangeLog-pr-13679.yml deleted file mode 100644 index 37b9d287b0..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13679.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "DeltaFire15" -delete-after: True -changes: - - rscadd: "Robots (anyone with the robotic_organism trait) have toxins damage replaced with system corruption. See the PR for details." diff --git a/html/changelogs/AutoChangeLog-pr-13680.yml b/html/changelogs/AutoChangeLog-pr-13680.yml deleted file mode 100644 index a1e58ac3a5..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13680.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "DeltaFire15" -delete-after: True -changes: - - bugfix: "A runtime caused by hallucinations is gone." diff --git a/html/changelogs/AutoChangeLog-pr-13681.yml b/html/changelogs/AutoChangeLog-pr-13681.yml deleted file mode 100644 index c51a7b51b0..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13681.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "DeltaFire15" -delete-after: True -changes: - - bugfix: "Supplypods shouldn't cause runtimes anymore, and shrapnel (pelletclouds) should work for them." diff --git a/html/changelogs/AutoChangeLog-pr-13682.yml b/html/changelogs/AutoChangeLog-pr-13682.yml deleted file mode 100644 index e5fececf4e..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13682.yml +++ /dev/null @@ -1,7 +0,0 @@ -author: "EmeraldSundisk" -delete-after: True -changes: - - bugfix: "Changes the area designations to be not varedited since the code didn't like that anymore" - - bugfix: "The cargo bay conveyor belts not only work with the shuttle now but go in the right direction to boot" - - tweak: "Slight visual adjustments to cargo in light of this" - - rscadd: "The arcade's got RAD carpet now" diff --git a/html/changelogs/AutoChangeLog-pr-13683.yml b/html/changelogs/AutoChangeLog-pr-13683.yml deleted file mode 100644 index da7ecab5c6..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13683.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "SandPoot" -delete-after: True -changes: - - code_imp: "Swaps the gift static blacklist with a global list one." diff --git a/html/changelogs/AutoChangeLog-pr-13685.yml b/html/changelogs/AutoChangeLog-pr-13685.yml deleted file mode 100644 index 10d53c9065..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13685.yml +++ /dev/null @@ -1,6 +0,0 @@ -author: "uomo91" -delete-after: True -changes: - - bugfix: "Fixed \"Show All\" tab in player panel logs being broken." - - bugfix: "Whispers, OOC, and various other things display differently in logs, visually distinguishing them from say logs." - - refactor: "Player panel logs will now show all logs chronologically, so you'll see commingled say and attack logs if you're on the \"Show All\" tab, etc..." diff --git a/html/changelogs/AutoChangeLog-pr-13686.yml b/html/changelogs/AutoChangeLog-pr-13686.yml deleted file mode 100644 index 72f9a2bd54..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13686.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Arturlang" -delete-after: True -changes: - - bugfix: "Hopefully fixes whitescreen issues for TGUI UI's by giving assets more time to get to the client" diff --git a/html/changelogs/AutoChangeLog-pr-13687.yml b/html/changelogs/AutoChangeLog-pr-13687.yml deleted file mode 100644 index e2baddbd2a..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13687.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "silicons" -delete-after: True -changes: - - rscadd: "tailed individuals can now target groin to intertwine tails on grab intent." diff --git a/html/changelogs/AutoChangeLog-pr-13688.yml b/html/changelogs/AutoChangeLog-pr-13688.yml deleted file mode 100644 index 4013705549..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13688.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "DeltaFire15" -delete-after: True -changes: - - bugfix: "Repeatable surgery steps can no longer cause an infinite loop if not completing the do_after" diff --git a/html/changelogs/AutoChangeLog-pr-13689.yml b/html/changelogs/AutoChangeLog-pr-13689.yml deleted file mode 100644 index eb476895e7..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13689.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "DeltaFire15" -delete-after: True -changes: - - bugfix: "Recharging APCs no longer use 0.2% of the power they should be using." - - bugfix: "APCs no longer always use as much power as they can for their cell, even if it is full." diff --git a/html/changelogs/AutoChangeLog-pr-13694.yml b/html/changelogs/AutoChangeLog-pr-13694.yml deleted file mode 100644 index 75a4bbb549..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13694.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "Acer202, with minor help from The0bserver" -delete-after: True -changes: - - rscadd: "After internal deliberation, CentCom has decided to run a limited reinstatement of public mining shuttles for use in more tried and true station classes. CentCom would like to remind you that this privilege is easily revoked, and that abuse may result in immediate detonation." - - rscadd: "Restores the mining shuttle on Pubby, Box, Delta, Meta, and Lambda Station." diff --git a/html/changelogs/AutoChangeLog-pr-13695.yml b/html/changelogs/AutoChangeLog-pr-13695.yml deleted file mode 100644 index 551d1f4df5..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13695.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "shellspeed1" -delete-after: True -changes: - - balance: "Lings now have infinite space for DNA." diff --git a/html/changelogs/AutoChangeLog-pr-13697.yml b/html/changelogs/AutoChangeLog-pr-13697.yml deleted file mode 100644 index 329a621c48..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13697.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Putnam3145" -delete-after: True -changes: - - balance: "Spontaneous brain trauma now requires minimum 5 players" diff --git a/html/changelogs/AutoChangeLog-pr-13698.yml b/html/changelogs/AutoChangeLog-pr-13698.yml deleted file mode 100644 index 10072ba948..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13698.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "timothyteakettle" -delete-after: True -changes: - - rscadd: "you can rebind communication hotkeys and they're the default now" diff --git a/html/changelogs/AutoChangeLog-pr-13699.yml b/html/changelogs/AutoChangeLog-pr-13699.yml deleted file mode 100644 index 5b2234224e..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13699.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "silicons" -delete-after: True -changes: - - tweak: "nanite resistances tweaked" - - rscadd: "new nanite programs added for locking the user out from being modified by consoles or antivirals." diff --git a/html/changelogs/AutoChangeLog-pr-13702.yml b/html/changelogs/AutoChangeLog-pr-13702.yml deleted file mode 100644 index 24f79c7cda..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13702.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Putnam3145" -delete-after: True -changes: - - tweak: "gear harness no longer magically covers up the body mechanically despite covering up nothing visually" diff --git a/html/changelogs/AutoChangeLog-pr-13703.yml b/html/changelogs/AutoChangeLog-pr-13703.yml deleted file mode 100644 index fd3ba2198f..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13703.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Putnam3145" -delete-after: True -changes: - - bugfix: "Dynamic vote no longer shows the none-storyteller." diff --git a/html/changelogs/AutoChangeLog-pr-13704.yml b/html/changelogs/AutoChangeLog-pr-13704.yml deleted file mode 100644 index 0ef8ca4a1d..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13704.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Putnam3145" -delete-after: True -changes: - - bugfix: "Emotes can properly be filtered for in TGUI." diff --git a/html/changelogs/AutoChangeLog-pr-13705.yml b/html/changelogs/AutoChangeLog-pr-13705.yml deleted file mode 100644 index c1b366aeb7..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13705.yml +++ /dev/null @@ -1,6 +0,0 @@ -author: "Putnam3145" -delete-after: True -changes: - - balance: "Rad collectors now get 1.25x as much energy from radiation" - - balance: "Rad collectors now put out 1.25x as much stored energy per tick" - - balance: "Above two rad collector changes give a total 56.25% power output increase" diff --git a/html/changelogs/AutoChangeLog-pr-13707.yml b/html/changelogs/AutoChangeLog-pr-13707.yml deleted file mode 100644 index a7dd977cca..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13707.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "DeltaFire15" -delete-after: True -changes: - - balance: "Robotpeople are now fully immune to the effects of alcohol (drunkness etc.)" - - tweak: "Renames the alcohol intolerance trait in the code to make what it does more clear." diff --git a/html/changelogs/AutoChangeLog-pr-13711.yml b/html/changelogs/AutoChangeLog-pr-13711.yml deleted file mode 100644 index e6fde23e8c..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13711.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "DeltaFire15" -delete-after: True -changes: - - tweak: "Medibots now ignore people they cannot help due to their biology." - - bugfix: "get_damaged_bodyparts() is no longer broken." diff --git a/html/changelogs/AutoChangeLog-pr-13713.yml b/html/changelogs/AutoChangeLog-pr-13713.yml deleted file mode 100644 index 36228f10c5..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13713.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "timothyteakettle" -delete-after: True -changes: - - rscadd: "all gas masks (but welding + glass) can be alt clicked to show/hide identity" diff --git a/html/changelogs/AutoChangeLog-pr-13715.yml b/html/changelogs/AutoChangeLog-pr-13715.yml deleted file mode 100644 index 70b22a9bc9..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13715.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "MrJWhit" -delete-after: True -changes: - - tweak: "Removes an opposum from the wall" diff --git a/html/changelogs/AutoChangeLog-pr-13717.yml b/html/changelogs/AutoChangeLog-pr-13717.yml deleted file mode 100644 index 268730c58e..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13717.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Acer202" -delete-after: True -changes: - - bugfix: "Main mining shuttle should no longer look at the public mining shuttle and attempt to dock ontop of it. Monastery shuttle should now function again." diff --git a/html/changelogs/AutoChangeLog-pr-13719.yml b/html/changelogs/AutoChangeLog-pr-13719.yml deleted file mode 100644 index c676072a35..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13719.yml +++ /dev/null @@ -1,6 +0,0 @@ -author: "shellspeed1" -delete-after: True -changes: - - rscadd: "Construction bags have been added, use them to carry all sorts of construction bits." - - rscadd: "A recipe has been added to cloth stacks to make material and construction bags." - - balance: "Material bags and construction bags are now available in engineering lockers." diff --git a/html/changelogs/AutoChangeLog-pr-13721.yml b/html/changelogs/AutoChangeLog-pr-13721.yml deleted file mode 100644 index 44b3c7283c..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13721.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "MrJWhit" -delete-after: True -changes: - - tweak: "Increased christmas event from 22th to 27th to 10th to 27th" diff --git a/html/changelogs/AutoChangeLog-pr-13725.yml b/html/changelogs/AutoChangeLog-pr-13725.yml deleted file mode 100644 index 5105de865e..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13725.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "silicons" -delete-after: True -changes: - - bugfix: "keybinds generate anti-collision bindings where necessary automatically now" diff --git a/html/changelogs/AutoChangeLog-pr-13733.yml b/html/changelogs/AutoChangeLog-pr-13733.yml deleted file mode 100644 index a10719fdc6..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13733.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "DeltaFire15" -delete-after: True -changes: - - bugfix: "Fixes the tail entwine messages displaying incorrectly." diff --git a/html/changelogs/AutoChangeLog-pr-13734.yml b/html/changelogs/AutoChangeLog-pr-13734.yml deleted file mode 100644 index a4ea9fdfac..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13734.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "keronshb" -delete-after: True -changes: - - bugfix: "Fixes Reflective Blobs" diff --git a/html/changelogs/AutoChangeLog-pr-13736.yml b/html/changelogs/AutoChangeLog-pr-13736.yml deleted file mode 100644 index dfa5e7cb66..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13736.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "DeltaFire15" -delete-after: True -changes: - - tweak: "Abductors now can use experimental organ replacement surgery on robots / synthetics." diff --git a/html/changelogs/AutoChangeLog-pr-13737.yml b/html/changelogs/AutoChangeLog-pr-13737.yml deleted file mode 100644 index 6c1121256f..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13737.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "qwertyquerty" -delete-after: True -changes: - - bugfix: "Flash the screen on climax" diff --git a/html/changelogs/AutoChangeLog-pr-13738.yml b/html/changelogs/AutoChangeLog-pr-13738.yml deleted file mode 100644 index f34f9d505a..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13738.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "EdgeLordExe, MoonFalcon" -delete-after: True -changes: - - balance: "Ported a bunch of heretic-related tweaks and changes from tg" diff --git a/html/changelogs/AutoChangeLog-pr-13739.yml b/html/changelogs/AutoChangeLog-pr-13739.yml deleted file mode 100644 index 6db29bf922..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13739.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "timothyteakettle" -delete-after: True -changes: - - bugfix: "custom eyes and tongues now properly carry across cloning" diff --git a/html/changelogs/AutoChangeLog-pr-13740.yml b/html/changelogs/AutoChangeLog-pr-13740.yml deleted file mode 100644 index 2bbd9b877a..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13740.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Linzolle" -delete-after: True -changes: - - bugfix: "lizard snouts are no longer *slightly* lighter than they are supposed to be." diff --git a/html/changelogs/AutoChangeLog-pr-13741.yml b/html/changelogs/AutoChangeLog-pr-13741.yml deleted file mode 100644 index c49015ab28..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13741.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "DeltaFire15" -delete-after: True -changes: - - bugfix: "Objectives are no longer very broken." diff --git a/html/changelogs/AutoChangeLog-pr-13743.yml b/html/changelogs/AutoChangeLog-pr-13743.yml deleted file mode 100644 index 00ff1a55bd..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13743.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "Linzolle" -delete-after: True -changes: - - bugfix: "entertainment monitors no longer invisible" - - rscadd: "entertainment monitors now light up and display text when motion is detected in thunderdome" diff --git a/html/changelogs/AutoChangeLog-pr-13745.yml b/html/changelogs/AutoChangeLog-pr-13745.yml deleted file mode 100644 index af6cb787b6..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13745.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Arturlang" -delete-after: True -changes: - - bugfix: "Handles more edge cases with construct soul returning" diff --git a/html/changelogs/AutoChangeLog-pr-13746.yml b/html/changelogs/AutoChangeLog-pr-13746.yml deleted file mode 100644 index 73c7ab4058..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13746.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "keronshb" -delete-after: True -changes: - - rscadd: "Adds more room to northwest maint" - - rscadd: "Adds a bridge between Atmos and the Turbine." diff --git a/html/changelogs/AutoChangeLog-pr-13747.yml b/html/changelogs/AutoChangeLog-pr-13747.yml deleted file mode 100644 index f8b6111c08..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13747.yml +++ /dev/null @@ -1,6 +0,0 @@ -author: "MrJWhit" -delete-after: True -changes: - - rscadd: "" - - tweak: "Added the ability for cargo to buy a large welding tank" - - imageadd: "Tweaked large tank reagent sprites to /tg/'s" diff --git a/html/changelogs/AutoChangeLog-pr-13750.yml b/html/changelogs/AutoChangeLog-pr-13750.yml deleted file mode 100644 index a89abdd440..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13750.yml +++ /dev/null @@ -1,6 +0,0 @@ -author: "EmeraldSundisk" -delete-after: True -changes: - - bugfix: "Fixes the conveyor belt issues in Delta Station's cargo wing" - - rscdel: "Removes some of the dirt around the affected area (presumably they would have cleaned it up while working on it)" - - rscadd: "Adds a floor light to fix the \"dark spot\" cargo had" diff --git a/html/changelogs/AutoChangeLog-pr-13751.yml b/html/changelogs/AutoChangeLog-pr-13751.yml deleted file mode 100644 index 2d8c964a14..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13751.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "silicons" -delete-after: True -changes: - - tweak: "text formatting now uses one character instead of two around the text to emphasize." diff --git a/html/changelogs/AutoChangeLog-pr-13752.yml b/html/changelogs/AutoChangeLog-pr-13752.yml deleted file mode 100644 index 2175ed469b..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13752.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "silicons" -delete-after: True -changes: - - balance: "stamina crit is only removed when at or under 100 stamina, rather than 140. stamina crit threshold is still at 140." diff --git a/html/changelogs/AutoChangeLog-pr-13753.yml b/html/changelogs/AutoChangeLog-pr-13753.yml deleted file mode 100644 index e752df79a0..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13753.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "silicons" -delete-after: True -changes: - - balance: "shoving yourself up now costs 50% more" diff --git a/html/changelogs/AutoChangeLog-pr-13755.yml b/html/changelogs/AutoChangeLog-pr-13755.yml deleted file mode 100644 index e50cd0199f..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13755.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "silicons" -delete-after: True -changes: - - rscadd: "colormates" diff --git a/html/changelogs/AutoChangeLog-pr-13757.yml b/html/changelogs/AutoChangeLog-pr-13757.yml deleted file mode 100644 index a36e67943e..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13757.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "timothyteakettle" -delete-after: True -changes: - - rscadd: "improvements to the automatic age gate" diff --git a/html/changelogs/AutoChangeLog-pr-13758.yml b/html/changelogs/AutoChangeLog-pr-13758.yml deleted file mode 100644 index fc4adc1614..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13758.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "timothyteakettle" -delete-after: True -changes: - - tweak: "custom holoforms are now accessible through an action instead of through verbs" - - tweak: "AI holoforms can now emote" diff --git a/html/changelogs/AutoChangeLog-pr-13761.yml b/html/changelogs/AutoChangeLog-pr-13761.yml deleted file mode 100644 index c8ab365fe3..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13761.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "silicons" -delete-after: True -changes: - - balance: "dna melt drops all items being destroying you" diff --git a/html/changelogs/AutoChangeLog-pr-13762.yml b/html/changelogs/AutoChangeLog-pr-13762.yml deleted file mode 100644 index 841e4d91c0..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13762.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "silicons" -delete-after: True -changes: - - tweak: "luxury shuttle no longer has noteleport" diff --git a/html/changelogs/AutoChangeLog-pr-13763.yml b/html/changelogs/AutoChangeLog-pr-13763.yml deleted file mode 100644 index 68eeaf1022..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13763.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Hatterhat" -delete-after: True -changes: - - tweak: "RPEDs now drop their lowest part tier first when quick-emptied (used inhand)." diff --git a/html/changelogs/AutoChangeLog-pr-13764.yml b/html/changelogs/AutoChangeLog-pr-13764.yml deleted file mode 100644 index 514705fd78..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13764.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Hatterhat" -delete-after: True -changes: - - tweak: "Exosuits sold on the Supply shuttle no longer leave wreckages." diff --git a/html/changelogs/AutoChangeLog-pr-13765.yml b/html/changelogs/AutoChangeLog-pr-13765.yml deleted file mode 100644 index 7ca1695d0f..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13765.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "MrJWhit" -delete-after: True -changes: - - tweak: "Updated jukebox sprite." diff --git a/html/changelogs/AutoChangeLog-pr-13766.yml b/html/changelogs/AutoChangeLog-pr-13766.yml deleted file mode 100644 index 8ba317afed..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13766.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "MrJWhit" -delete-after: True -changes: - - tweak: "Updates our dark gygax sprites to /tg/'s" diff --git a/html/changelogs/AutoChangeLog-pr-13767.yml b/html/changelogs/AutoChangeLog-pr-13767.yml deleted file mode 100644 index 2d38af77e3..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13767.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "MrJWhit" -delete-after: True -changes: - - tweak: "Updated meat icons" diff --git a/html/changelogs/AutoChangeLog-pr-13769.yml b/html/changelogs/AutoChangeLog-pr-13769.yml deleted file mode 100644 index 04d1cbc11c..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13769.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "MrJWhit" -delete-after: True -changes: - - rscadd: "Adds a goose bar sign" diff --git a/html/changelogs/AutoChangeLog-pr-13771.yml b/html/changelogs/AutoChangeLog-pr-13771.yml deleted file mode 100644 index e450508681..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13771.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "MrJWhit" -delete-after: True -changes: - - tweak: "Donut boxes show what's inside of them now" diff --git a/html/changelogs/AutoChangeLog-pr-13772.yml b/html/changelogs/AutoChangeLog-pr-13772.yml deleted file mode 100644 index 109360cace..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13772.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "silicons" -delete-after: True -changes: - - rscadd: "players can now respawn/return to lobby as a ghost after a 15 minute (default) delay and rejoin on another character with some/many restrictions" - - rscadd: "cryo now preserves everything" diff --git a/html/changelogs/AutoChangeLog-pr-13773.yml b/html/changelogs/AutoChangeLog-pr-13773.yml deleted file mode 100644 index 498ac42561..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13773.yml +++ /dev/null @@ -1,6 +0,0 @@ -author: "Trilbyspaceclone" -delete-after: True -changes: - - tweak: "Zeolites now use gold rather then uranium for catalyst" - - tweak: "Zeolites are not as hard to make ph wise" - - tweak: "Making Zeolites heats up the beaker less allowing for better control" diff --git a/html/changelogs/AutoChangeLog-pr-13774.yml b/html/changelogs/AutoChangeLog-pr-13774.yml deleted file mode 100644 index ea3af475f2..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13774.yml +++ /dev/null @@ -1,6 +0,0 @@ -author: "keronshb" -delete-after: True -changes: - - rscadd: "Re-adds the Clown Car to the clown uplink" - - balance: "15 >16 TC cost" - - balance: "bonks on external airlocks" diff --git a/html/changelogs/AutoChangeLog-pr-13776.yml b/html/changelogs/AutoChangeLog-pr-13776.yml deleted file mode 100644 index 773aae9745..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13776.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "DeltaFire15" -delete-after: True -changes: - - tweak: "AIs now only have to kill people once instead of permanently." diff --git a/html/changelogs/AutoChangeLog-pr-13777.yml b/html/changelogs/AutoChangeLog-pr-13777.yml deleted file mode 100644 index 0013a93ef5..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13777.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "The Grinch" -delete-after: True -changes: - - rscdel: "infinite presents from hilbert hotel" diff --git a/html/changelogs/AutoChangeLog-pr-13784.yml b/html/changelogs/AutoChangeLog-pr-13784.yml deleted file mode 100644 index df8cf7ad57..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13784.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "MrJWhit" -delete-after: True -changes: - - bugfix: "Effects can no longer trigger landmines" diff --git a/html/changelogs/AutoChangeLog-pr-13785.yml b/html/changelogs/AutoChangeLog-pr-13785.yml deleted file mode 100644 index 973fb3f74e..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13785.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "Putnam3145" -delete-after: True -changes: - - tweak: "Grab bag works as advertised." - - balance: "Xeno threat in dynamic tripled." diff --git a/html/changelogs/AutoChangeLog-pr-13790.yml b/html/changelogs/AutoChangeLog-pr-13790.yml deleted file mode 100644 index 45ebf00ee7..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13790.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "MrJWhit" -delete-after: True -changes: - - tweak: "Replaced engineering fuel tank with a large fuel tank" - - tweak: "Changed access to sec suit storage from armory access in every map to other security access" diff --git a/html/changelogs/AutoChangeLog-pr-13796.yml b/html/changelogs/AutoChangeLog-pr-13796.yml deleted file mode 100644 index 827cbf7443..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13796.yml +++ /dev/null @@ -1,9 +0,0 @@ -author: "keronshb" -delete-after: True -changes: - - rscadd: "Adds a reduced stamina buffer for SCarp users" - - rscadd: "Gives SCarp users a better parry" - - rscadd: "Adds the SCarp bundle which includes a bo staff" - - rscadd: "Lets Carp costumes carry Bo Staffs" - - balance: "reduces the stamina damage of scarp slightly" - - balance: "reduced the blockchance of the bo staff" diff --git a/html/changelogs/AutoChangeLog-pr-13799.yml b/html/changelogs/AutoChangeLog-pr-13799.yml deleted file mode 100644 index 4f8168aa45..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13799.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "silicons" -delete-after: True -changes: - - bugfix: "ninja gloves no longer hardstun" - - balance: "ninja gloves now cost half as much to use to compensate" diff --git a/html/changelogs/AutoChangeLog-pr-13801.yml b/html/changelogs/AutoChangeLog-pr-13801.yml deleted file mode 100644 index e33b6f4056..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13801.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "SandPoot" -delete-after: True -changes: - - refactor: "Changes the limb grower a lot." - - bugfix: "\"Limb\" costs on limbgrower are actually displayed like it was meant to all along." diff --git a/html/changelogs/AutoChangeLog-pr-13803.yml b/html/changelogs/AutoChangeLog-pr-13803.yml deleted file mode 100644 index 68791ea87a..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13803.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "BlueWildrose" -delete-after: True -changes: - - balance: "The traitor AI can no longer activate the doomsday device while carded." diff --git a/html/changelogs/AutoChangeLog-pr-13804.yml b/html/changelogs/AutoChangeLog-pr-13804.yml deleted file mode 100644 index 6b6d56dc5c..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13804.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "timothyteakettle" -delete-after: True -changes: - - bugfix: "circuits get pin data proc is sanitized when text is returned as data" diff --git a/html/changelogs/AutoChangeLog-pr-13808.yml b/html/changelogs/AutoChangeLog-pr-13808.yml deleted file mode 100644 index fc97da6161..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13808.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "Putnam3145" -delete-after: True -changes: - - tweak: "You can now exit polycircuit input" - - bugfix: "Polycircuits now check for range" diff --git a/html/changelogs/AutoChangeLog-pr-13809.yml b/html/changelogs/AutoChangeLog-pr-13809.yml deleted file mode 100644 index d63ee33ff1..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13809.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Putnam3145" -delete-after: True -changes: - - tweak: "Gear harness now covers nothing." diff --git a/html/changelogs/AutoChangeLog-pr-13811.yml b/html/changelogs/AutoChangeLog-pr-13811.yml deleted file mode 100644 index da2691d8f5..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13811.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "BlackMajor" -delete-after: True -changes: - - tweak: "Cyborg hypospray no longer injects if it means OD'ing while on help intent." diff --git a/html/changelogs/AutoChangeLog-pr-13813.yml b/html/changelogs/AutoChangeLog-pr-13813.yml deleted file mode 100644 index ce283eb788..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13813.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "silicons" -delete-after: True -changes: - - tweak: "command headsets are 120% instead of 160%" - - bugfix: "no more emote italics" diff --git a/html/changelogs/AutoChangeLog-pr-13814.yml b/html/changelogs/AutoChangeLog-pr-13814.yml deleted file mode 100644 index 9bd0be5ac0..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13814.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Xantholne" -delete-after: True -changes: - - bugfix: "Twin Sword Sheaths have an equipment icon and icon when worn now and make a sound when sheathed/unsheathed" diff --git a/html/changelogs/AutoChangeLog-pr-13815.yml b/html/changelogs/AutoChangeLog-pr-13815.yml deleted file mode 100644 index 630ca9d7f7..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13815.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Yakumo Chen" -delete-after: True -changes: - - balance: "Slime Jelly is no longer obtainable from slimepeople. Go ask Xenobio" diff --git a/html/changelogs/AutoChangeLog-pr-13819.yml b/html/changelogs/AutoChangeLog-pr-13819.yml deleted file mode 100644 index 77b1ec9a3d..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13819.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "Hatterhat" -delete-after: True -changes: - - bugfix: "The survival dagger light on the sprite now actually turns on and off." - - balance: "The survival dagger in the glaive kit that can also be bought by itself is now better at butchering things." diff --git a/html/changelogs/AutoChangeLog-pr-13822.yml b/html/changelogs/AutoChangeLog-pr-13822.yml deleted file mode 100644 index 743bdb500b..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13822.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "DeltaFire15" -delete-after: True -changes: - - bugfix: "Bloodcult stunhands now work against clockies like they were supposed to instead of hardstunning." diff --git a/html/changelogs/AutoChangeLog-pr-13823.yml b/html/changelogs/AutoChangeLog-pr-13823.yml deleted file mode 100644 index 35d70339f7..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13823.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "MrJWhit" -delete-after: True -changes: - - tweak: "Christmas starts on the 18th now" diff --git a/html/changelogs/AutoChangeLog-pr-13825.yml b/html/changelogs/AutoChangeLog-pr-13825.yml deleted file mode 100644 index ae82c1cd8e..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13825.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "raspy-on-osu" -delete-after: True -changes: - - tweak: "windoor open length" diff --git a/html/changelogs/AutoChangeLog-pr-13828.yml b/html/changelogs/AutoChangeLog-pr-13828.yml deleted file mode 100644 index 282b8a01e0..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13828.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "timothyteakettle" -delete-after: True -changes: - - bugfix: "you can now only entwine tails with people who have a tail" diff --git a/html/changelogs/AutoChangeLog-pr-13830.yml b/html/changelogs/AutoChangeLog-pr-13830.yml deleted file mode 100644 index c126670246..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13830.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "timothyteakettle" -delete-after: True -changes: - - bugfix: "apids render now" diff --git a/html/changelogs/AutoChangeLog-pr-13832.yml b/html/changelogs/AutoChangeLog-pr-13832.yml deleted file mode 100644 index 756edb7657..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13832.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Arturlang" -delete-after: True -changes: - - bugfix: "Bloodsuckers tresspass ability can no longer work while they are not awake." diff --git a/html/changelogs/AutoChangeLog-pr-13833.yml b/html/changelogs/AutoChangeLog-pr-13833.yml deleted file mode 100644 index 8312c7a5a3..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13833.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "kiwedespars" -delete-after: True -changes: - - balance: "balanced bone gauntlets." diff --git a/html/changelogs/AutoChangeLog-pr-13834.yml b/html/changelogs/AutoChangeLog-pr-13834.yml deleted file mode 100644 index 46cedff5d5..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13834.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "zeroisthebiggay" -delete-after: True -changes: - - rscadd: "Traitor assistants can now purchase the patented POGBox! Put TC into it for even higher damage!" diff --git a/html/changelogs/AutoChangeLog-pr-13835.yml b/html/changelogs/AutoChangeLog-pr-13835.yml deleted file mode 100644 index 0ac90921c3..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13835.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "silicons" -delete-after: True -changes: - - rscadd: "pugilists can now parry" diff --git a/html/changelogs/AutoChangeLog-pr-13837.yml b/html/changelogs/AutoChangeLog-pr-13837.yml deleted file mode 100644 index eda0b77a83..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13837.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "silicons" -delete-after: True -changes: - - tweak: "anyone new to the server is lucky enough to have their sprint default to toggle instead of hold" diff --git a/html/changelogs/AutoChangeLog-pr-13838.yml b/html/changelogs/AutoChangeLog-pr-13838.yml deleted file mode 100644 index deff5913b3..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13838.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "silicons" -delete-after: True -changes: - - bugfix: "things in DEATHCOMA do not deathgasp on death" diff --git a/html/changelogs/AutoChangeLog-pr-13844.yml b/html/changelogs/AutoChangeLog-pr-13844.yml deleted file mode 100644 index cd1f7a25d4..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13844.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Arturlang" -delete-after: True -changes: - - bugfix: "Fixes the mint machine's UI" diff --git a/html/changelogs/AutoChangeLog-pr-13845.yml b/html/changelogs/AutoChangeLog-pr-13845.yml deleted file mode 100644 index d10485ae9f..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13845.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "silicons" -delete-after: True -changes: - - bugfix: "dullahans enabled" diff --git a/html/changelogs/AutoChangeLog-pr-13846.yml b/html/changelogs/AutoChangeLog-pr-13846.yml deleted file mode 100644 index 68e013acf0..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13846.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "DeltaFire15" -delete-after: True -changes: - - balance: "Fleshmend, Anatomic Panacea and bloodsucker healing now work for Synths / IPCs." diff --git a/html/changelogs/AutoChangeLog-pr-13850.yml b/html/changelogs/AutoChangeLog-pr-13850.yml deleted file mode 100644 index 64c47ade40..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13850.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "DeltaFire15" -delete-after: True -changes: - - bugfix: "Self-fueling weldingtools recharge fuel properly again." - - bugfix: "Brass welders now actually recharge faster than experimental ones." diff --git a/html/changelogs/AutoChangeLog-pr-13855.yml b/html/changelogs/AutoChangeLog-pr-13855.yml deleted file mode 100644 index 0eed195acd..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13855.yml +++ /dev/null @@ -1,8 +0,0 @@ -author: "Chiirno" -delete-after: True -changes: - - rscadd: "Paramedics office and Surgery Storage Room" - - tweak: "Remodeled the surgery room, as well as shrunk Morgue and Starboard Emergency Storage. -Fiddled with some areas for better map edit clarity and fixed one runtime in Vacant Office A." - - imageadd: "Added the paramedic closet sprite, a paramedic colored medical3 closet." - - code_imp: "Added a paramedic closet, which is the standard medical3 closet with their suit, a pinpointer, and a crew monitor added." diff --git a/html/changelogs/AutoChangeLog-pr-13856.yml b/html/changelogs/AutoChangeLog-pr-13856.yml deleted file mode 100644 index 583fb38cc2..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13856.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "Putnam3145" -delete-after: True -changes: - - bugfix: "gear harness alt-click is now sane" - - code_imp: "rolldown() and toggle_jumpsuit_adjust() now no longer mix behavior-that-should-be-overridden and behavior-that-shouldn't-be-overridden in ways that make no sense." diff --git a/html/changelogs/AutoChangeLog-pr-13859.yml b/html/changelogs/AutoChangeLog-pr-13859.yml deleted file mode 100644 index 2d6dc5c25a..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13859.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "timothyteakettle" -delete-after: True -changes: - - rscadd: "adds unlockable loadout items, corresponding category in loadouts, etc" diff --git a/html/changelogs/AutoChangeLog-pr-13867.yml b/html/changelogs/AutoChangeLog-pr-13867.yml deleted file mode 100644 index dd1e44e64c..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13867.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Putnam3145" -delete-after: True -changes: - - balance: "Zeolites now only generate 1/5 the heat when reacting and don't require a catalyst." diff --git a/html/changelogs/AutoChangeLog-pr-13869.yml b/html/changelogs/AutoChangeLog-pr-13869.yml deleted file mode 100644 index 5d4ca4817c..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13869.yml +++ /dev/null @@ -1,6 +0,0 @@ -author: "timothyteakettle" -delete-after: True -changes: - - tweak: "slime puddles are no longer layered down one layer" - - tweak: "you cannot tackle with two paralysed arms" - - tweak: "tackling with a single paralysed arm lowers your tackle roll by 2" diff --git a/html/changelogs/AutoChangeLog-pr-13870.yml b/html/changelogs/AutoChangeLog-pr-13870.yml deleted file mode 100644 index 820c524224..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13870.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "silicons" -delete-after: True -changes: - - bugfix: "Magrifle ammo no longer glows." diff --git a/html/changelogs/AutoChangeLog-pr-13871.yml b/html/changelogs/AutoChangeLog-pr-13871.yml deleted file mode 100644 index f667abe816..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13871.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "NT Cleaning Crews On Break" -delete-after: True -changes: - - rscadd: "Most kinds of dirt, grime, and debris are now persistent. Get to work, jannies." - - rscadd: "Dirt can now be removed by tile replacements. Other cleanable decals can't, though." diff --git a/html/changelogs/AutoChangeLog-pr-13873.yml b/html/changelogs/AutoChangeLog-pr-13873.yml deleted file mode 100644 index 320a5b69a4..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13873.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "BlueWildrose" -delete-after: True -changes: - - tweak: "Nyctophobia quirk now has some light lag compensation." diff --git a/html/changelogs/AutoChangeLog-pr-13881.yml b/html/changelogs/AutoChangeLog-pr-13881.yml deleted file mode 100644 index 0ad2d3afc8..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13881.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "BlueWildrose" -delete-after: True -changes: - - rscadd: "PubbyStation now has two Christmas Tree spawners." diff --git a/html/changelogs/AutoChangeLog-pr-13882.yml b/html/changelogs/AutoChangeLog-pr-13882.yml deleted file mode 100644 index fe545bdc23..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13882.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "ERP mains" -delete-after: True -changes: - - rscadd: "Subtler Around Table is now a verb" diff --git a/html/changelogs/AutoChangeLog-pr-13883.yml b/html/changelogs/AutoChangeLog-pr-13883.yml deleted file mode 100644 index 884adafffe..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13883.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "silicons" -delete-after: True -changes: - - rscadd: "set-pose has been added" - - tweak: "temporary flavor text renamed to set pose, fully visible in examine" diff --git a/html/changelogs/AutoChangeLog-pr-13884.yml b/html/changelogs/AutoChangeLog-pr-13884.yml deleted file mode 100644 index f2b9233690..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13884.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "Hatterhat" -delete-after: True -changes: - - rscadd: "The dock-silver standard set by Box and Meta has been enforced across maps in rotation (Delta, Pubby, Lambda)." - - bugfix: "The Box whiteship now has its missing tiny fan back." diff --git a/html/changelogs/AutoChangeLog-pr-13885.yml b/html/changelogs/AutoChangeLog-pr-13885.yml deleted file mode 100644 index 88c23d30f1..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13885.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "raspy-on-osu" -delete-after: True -changes: - - tweak: "space heater heating range and power" diff --git a/html/changelogs/AutoChangeLog-pr-13887.yml b/html/changelogs/AutoChangeLog-pr-13887.yml deleted file mode 100644 index 83069a4e64..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13887.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "HeroWithYay" -delete-after: True -changes: - - bugfix: "Changed description of Necrotizing Fasciitis symptom." diff --git a/html/changelogs/AutoChangeLog-pr-13892.yml b/html/changelogs/AutoChangeLog-pr-13892.yml deleted file mode 100644 index 976b395826..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13892.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "silicons" -delete-after: True -changes: - - balance: "changeling combat mutations rebalanced. most of them take chemicals to upkeep now." diff --git a/html/changelogs/AutoChangeLog-pr-13893.yml b/html/changelogs/AutoChangeLog-pr-13893.yml deleted file mode 100644 index 9eb7a3f79d..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13893.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "silicons" -delete-after: True -changes: - - tweak: "Meth and changeling adrenals no longer ignore all slowdowns, rather damage slowdowns." diff --git a/html/changelogs/AutoChangeLog-pr-13894.yml b/html/changelogs/AutoChangeLog-pr-13894.yml deleted file mode 100644 index 31e1e66328..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13894.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "silicons" -delete-after: True -changes: - - tweak: "temperature slowdown divisor nerfed to 35 from 20." diff --git a/html/changelogs/AutoChangeLog-pr-13896.yml b/html/changelogs/AutoChangeLog-pr-13896.yml deleted file mode 100644 index f733efa09d..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13896.yml +++ /dev/null @@ -1,6 +0,0 @@ -author: "Sonic121x" -delete-after: True -changes: - - rscadd: "alarm ert hardsuit sprite for naga and canine" - - tweak: "adjust the naga ert hardsuit to cover the hand" - - bugfix: "cydonia hardsuit helmet" diff --git a/html/changelogs/AutoChangeLog-pr-13897.yml b/html/changelogs/AutoChangeLog-pr-13897.yml deleted file mode 100644 index 05637b0908..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13897.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "keronshb" -delete-after: True -changes: - - bugfix: "Allows Energy Bola to be caught" - - balance: "This also allows them to be dropped/picked up." diff --git a/html/changelogs/AutoChangeLog-pr-13898.yml b/html/changelogs/AutoChangeLog-pr-13898.yml deleted file mode 100644 index cb4e0fd52d..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13898.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "Putnam3145" -delete-after: True -changes: - - balance: "Regen coma now puts into a coma even from crit or while unconscious." - - bugfix: "Regen coma now properly weakens while asleep." diff --git a/html/changelogs/AutoChangeLog-pr-13900.yml b/html/changelogs/AutoChangeLog-pr-13900.yml deleted file mode 100644 index a25c8af1ed..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13900.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Xantholne" -delete-after: True -changes: - - rscadd: "New parrots from the RP server, can be found in Bird Crate in Cargo" diff --git a/html/changelogs/AutoChangeLog-pr-13902.yml b/html/changelogs/AutoChangeLog-pr-13902.yml deleted file mode 100644 index ca548d816f..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13902.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "timothyteakettle" -delete-after: True -changes: - - rscadd: "avian/digitigrade legs have been added for slimes" diff --git a/html/changelogs/AutoChangeLog-pr-13903.yml b/html/changelogs/AutoChangeLog-pr-13903.yml deleted file mode 100644 index fd66c4b0c6..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13903.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "DeltaFire15" -delete-after: True -changes: - - bugfix: "Changelings no longer double-deathgasp when activating the regen stasis ability while not dead." diff --git a/html/changelogs/AutoChangeLog-pr-13904.yml b/html/changelogs/AutoChangeLog-pr-13904.yml deleted file mode 100644 index 90f7d77e17..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13904.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "DeltaFire15" -delete-after: True -changes: - - tweak: "Instant summons can no longer do wacky stuff with disposals (and nukes)." diff --git a/html/changelogs/AutoChangeLog-pr-13905.yml b/html/changelogs/AutoChangeLog-pr-13905.yml deleted file mode 100644 index 16b464bc20..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13905.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "zeroisthebiggay" -delete-after: True -changes: - - imageadd: "cool codex cicatrix inhands" diff --git a/html/changelogs/AutoChangeLog-pr-13909.yml b/html/changelogs/AutoChangeLog-pr-13909.yml deleted file mode 100644 index e4f0f1e401..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13909.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "silicons" -delete-after: True -changes: - - rscadd: "Twitch Plays: Clown Car" diff --git a/html/changelogs/AutoChangeLog-pr-13911.yml b/html/changelogs/AutoChangeLog-pr-13911.yml deleted file mode 100644 index 9e96103b2b..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13911.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "raspy-on-osu" -delete-after: True -changes: - - spellcheck: "salicylic acid" diff --git a/html/changelogs/AutoChangeLog-pr-13913.yml b/html/changelogs/AutoChangeLog-pr-13913.yml deleted file mode 100644 index a25e054a6f..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13913.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "keronshb" -delete-after: True -changes: - - bugfix: "Fixes the parry data for scarp" diff --git a/html/changelogs/AutoChangeLog-pr-13914.yml b/html/changelogs/AutoChangeLog-pr-13914.yml deleted file mode 100644 index b9efe2a893..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13914.yml +++ /dev/null @@ -1,6 +0,0 @@ -author: "EmeraldSundisk" -delete-after: True -changes: - - rscadd: "Adds a few new area designations primarily for CogStation, incorporates them into said map" - - tweak: "Reorganizes some area designations for ease of use, along with renaming the central \"Router\" to \"Routing Depot\"" - - bugfix: "Fixes an incorrectly designated area in CogStation" diff --git a/html/changelogs/AutoChangeLog-pr-13915.yml b/html/changelogs/AutoChangeLog-pr-13915.yml deleted file mode 100644 index 2853dcb9ad..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13915.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Xantholne" -delete-after: True -changes: - - bugfix: "Fixed new birds changing back to basic parrot when sitting" diff --git a/html/changelogs/AutoChangeLog-pr-13916.yml b/html/changelogs/AutoChangeLog-pr-13916.yml deleted file mode 100644 index 0ec04707a5..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13916.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "timothyteakettle" -delete-after: True -changes: - - rscadd: "re-adds the holoform verb for people who want to use it over going through the char list" diff --git a/html/changelogs/AutoChangeLog-pr-13917.yml b/html/changelogs/AutoChangeLog-pr-13917.yml deleted file mode 100644 index 5eabd640f7..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13917.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "timothyteakettle" -delete-after: True -changes: - - rscadd: "*squeak" diff --git a/html/changelogs/AutoChangeLog-pr-13924.yml b/html/changelogs/AutoChangeLog-pr-13924.yml deleted file mode 100644 index 71092c567d..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13924.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "SandPoot" -delete-after: True -changes: - - tweak: "You can attack a pile of money on the floor with your id to put it all in quickly." diff --git a/html/changelogs/AutoChangeLog-pr-13926.yml b/html/changelogs/AutoChangeLog-pr-13926.yml deleted file mode 100644 index 4e92bacdb4..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13926.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "MrJWhit" -delete-after: True -changes: - - rscdel: "Removes the screen flashing on climax." diff --git a/html/changelogs/AutoChangeLog-pr-13930.yml b/html/changelogs/AutoChangeLog-pr-13930.yml deleted file mode 100644 index 282f3fa30a..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13930.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "MrJWhit" -delete-after: True -changes: - - tweak: "Small fixes on security on boxstation" diff --git a/html/changelogs/AutoChangeLog-pr-13931.yml b/html/changelogs/AutoChangeLog-pr-13931.yml deleted file mode 100644 index fd903f2d6e..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13931.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "shellspeed1" -delete-after: True -changes: - - rscadd: "Adds the disposable sentry gun from tg for 11tc each." diff --git a/html/changelogs/AutoChangeLog-pr-13932.yml b/html/changelogs/AutoChangeLog-pr-13932.yml deleted file mode 100644 index 955d9f5741..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13932.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "shellspeed1" -delete-after: True -changes: - - tweak: "Xenomorph powers now list plasma cost in their description." diff --git a/html/changelogs/AutoChangeLog-pr-13934.yml b/html/changelogs/AutoChangeLog-pr-13934.yml deleted file mode 100644 index 74f8ece6fc..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13934.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "shellspeed1" -delete-after: True -changes: - - rscadd: "The bluespace navigation gigabeacon design has been added to shuttle research for those wanting to take their ships around space more." diff --git a/html/changelogs/AutoChangeLog-pr-13936.yml b/html/changelogs/AutoChangeLog-pr-13936.yml deleted file mode 100644 index 9b15d2232a..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13936.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "BlueWildrose" -delete-after: True -changes: - - bugfix: "Polychromic hoodies that were obtained from the loadout have functional colorable hoods now." diff --git a/html/changelogs/AutoChangeLog-pr-13938.yml b/html/changelogs/AutoChangeLog-pr-13938.yml deleted file mode 100644 index 34d76a190b..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13938.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "timothyteakettle" -delete-after: True -changes: - - rscadd: "anthromorphic synth species" diff --git a/html/changelogs/AutoChangeLog-pr-13940.yml b/html/changelogs/AutoChangeLog-pr-13940.yml deleted file mode 100644 index 60cbcc9a19..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13940.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Chiirno" -delete-after: True -changes: - - rscadd: "Added the paramedics EVA suit as a purchase from the cargo console." diff --git a/html/changelogs/AutoChangeLog-pr-13941.yml b/html/changelogs/AutoChangeLog-pr-13941.yml deleted file mode 100644 index 5a72376ebb..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13941.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Chiirno" -delete-after: True -changes: - - bugfix: "Portable Chem Mixer now researchable from biotech node." diff --git a/html/changelogs/AutoChangeLog-pr-13943.yml b/html/changelogs/AutoChangeLog-pr-13943.yml deleted file mode 100644 index 405d6b0d93..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13943.yml +++ /dev/null @@ -1,6 +0,0 @@ -author: "Hatterhat" -delete-after: True -changes: - - spellcheck: "Utility uniforms now comply with the \"nonproper equipment names\" thing." - - bugfix: "The CapDrobe now allows the captain to get his own clothes for free. Probably." - - tweak: "All captains' clothes now offer 15 woundarmor, up from the 5. Because apparently only the suit and tie and its suitskirt subtype have this wound armor, which is dumb." diff --git a/html/changelogs/AutoChangeLog-pr-13945.yml b/html/changelogs/AutoChangeLog-pr-13945.yml deleted file mode 100644 index 91b8bb9498..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13945.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "DeltaFire15" -delete-after: True -changes: - - rscadd: "IPC cells & power cords are now printable after they are researched." diff --git a/html/changelogs/AutoChangeLog-pr-13946.yml b/html/changelogs/AutoChangeLog-pr-13946.yml deleted file mode 100644 index d7f3e15024..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13946.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Ryll/Shaps" -delete-after: True -changes: - - admin: "Fixed an issue with player logs becoming confused when someone triggers multiple events within one second (like being attacked by two people at the same time) that would cause holes in the logs" diff --git a/html/changelogs/AutoChangeLog-pr-13947.yml b/html/changelogs/AutoChangeLog-pr-13947.yml deleted file mode 100644 index 0388efc701..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13947.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Chiirno" -delete-after: True -changes: - - tweak: "Chem masters can now dispense 20 instances of its outputs instead of 10." diff --git a/html/changelogs/AutoChangeLog-pr-13948.yml b/html/changelogs/AutoChangeLog-pr-13948.yml deleted file mode 100644 index 73c5ba6b1a..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13948.yml +++ /dev/null @@ -1,11 +0,0 @@ -author: "necromanceranne" -delete-after: True -changes: - - rscadd: "Bokken now come in two lengths; full and wakizashi, and two varieties: wood and ironwood. They have different stats for all four." - - rscadd: "Bokken require menu crafting and part construction, as well as more complicated materials." - - tweak: "Bokken (long and short) require wood, cloth and leather to craft with a hatchet and screwdriver." - - tweak: "Ironwood bokken (long and short) require ironcap logs, cloth and leather to craft with a hatchet, screwdriver and welder." - - balance: "Twin sheathes can only fit a pair of blades (longsword + shortsword) or they can fit two shortswords." - - bugfix: "Fixed a twin sheath runtime." - - imageadd: "A lot of bokken related sprites received an overhaul. Added overlay sprites for weapons sheathed in the twin sheathes." - - imageadd: "The extradimensional blade received improved sprites for inhands/back sprites." diff --git a/html/changelogs/AutoChangeLog-pr-13950.yml b/html/changelogs/AutoChangeLog-pr-13950.yml deleted file mode 100644 index 28affe7ebe..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13950.yml +++ /dev/null @@ -1,6 +0,0 @@ -author: "TripleShades" -delete-after: True -changes: - - rscadd: "New Paramedic Office next to Genetics where the old Genetics Reception used to be -change: Surgery, Surgery Observation, and Recovery Hall layout revamped drastically -change: Maints below Surgery lowered by one tile to recover lost tile space from Surgery expansion" diff --git a/html/changelogs/AutoChangeLog-pr-13951.yml b/html/changelogs/AutoChangeLog-pr-13951.yml deleted file mode 100644 index fe3ac84523..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13951.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "BlueWildrose" -delete-after: True -changes: - - tweak: "You can now have a max-roundstart-dicksize-config inch long johnson before you start suffering blood loss and slowdowns instead of a 20 inch one." diff --git a/html/changelogs/AutoChangeLog-pr-13954.yml b/html/changelogs/AutoChangeLog-pr-13954.yml deleted file mode 100644 index a8751d66ca..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13954.yml +++ /dev/null @@ -1,6 +0,0 @@ -author: "EmeraldSundisk" -delete-after: True -changes: - - rscadd: "Adds a new \"Computer Core\" area designation for CogStation" - - bugfix: "Fixes some missing area strings" - - tweak: "Replaces some firelocks with directional ones as to ensure desks/counters can still be accessed" diff --git a/html/changelogs/AutoChangeLog-pr-13955.yml b/html/changelogs/AutoChangeLog-pr-13955.yml deleted file mode 100644 index 1d43dd6b95..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13955.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "Chiirno" -delete-after: True -changes: - - tweak: "Nightmare now deals additional damage to most light sources." - - bugfix: "Nightmare now one-shots miners beacons and glowshrooms" diff --git a/html/changelogs/AutoChangeLog-pr-13959.yml b/html/changelogs/AutoChangeLog-pr-13959.yml deleted file mode 100644 index 1303768344..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13959.yml +++ /dev/null @@ -1,6 +0,0 @@ -author: "necromanceranne" -delete-after: True -changes: - - bugfix: "You can now make all the variants of the bokken." - - bugfix: "Removes a duplicate sprite." - - tweak: "Renames all instances of 'ironwood' to 'steelwood'." diff --git a/html/changelogs/AutoChangeLog-pr-13961.yml b/html/changelogs/AutoChangeLog-pr-13961.yml deleted file mode 100644 index f0a6cc267d..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13961.yml +++ /dev/null @@ -1,6 +0,0 @@ -author: "MrJWhit" -delete-after: True -changes: - - tweak: "Removed the wires connecting the AI from the rest of the station on cogstation." - - tweak: "Fixes experimenter on cogstation." - - tweak: "Less pipes in the overall area in toxins on cogstation" diff --git a/html/changelogs/AutoChangeLog-pr-13965.yml b/html/changelogs/AutoChangeLog-pr-13965.yml deleted file mode 100644 index 69f5a90122..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13965.yml +++ /dev/null @@ -1,7 +0,0 @@ -author: "Xantholne" -delete-after: True -changes: - - rscadd: "You can now tuck disky into bed" - - rscadd: "You can now make beds by applying a bed sheet to them" - - rscadd: "You can now tuck in pai cards into bed" - - rscadd: "Added bed tucking element, can be added to any held object to allow tucking into beds" diff --git a/html/changelogs/AutoChangeLog-pr-13967.yml b/html/changelogs/AutoChangeLog-pr-13967.yml deleted file mode 100644 index d2e580b531..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13967.yml +++ /dev/null @@ -1,6 +0,0 @@ -author: "keronshb" -delete-after: True -changes: - - balance: "Blob Resource Tower to 2 points per instead of 1 point per." - - balance: "Blob Factory Towers can be placed 5 tiles apart instead of 7." - - bugfix: "Fixes Blobbernaut Factories consuming Factories if no naut is chosen." diff --git a/html/changelogs/AutoChangeLog-pr-13968.yml b/html/changelogs/AutoChangeLog-pr-13968.yml deleted file mode 100644 index e3ce9e4615..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13968.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Arturlang" -delete-after: True -changes: - - tweak: "Prayers to admins now do a wee ding sound for all prayers, instead of just chaplains" diff --git a/html/changelogs/AutoChangeLog-pr-13970.yml b/html/changelogs/AutoChangeLog-pr-13970.yml deleted file mode 100644 index 54d8d9992d..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13970.yml +++ /dev/null @@ -1,8 +0,0 @@ -author: "TripleShades" -delete-after: True -changes: - - rscadd: "Firelock to Surgery Bay drapes -change: Swapped Nanomed and Fire Alarm button locations in both Surgery Bays -change: Removes the double mirror in both Surgery Bays to be a singular mirror -change: Moved an intercom to not be doorstuck below Paramedical Office -remove: One Surgery Observation Fire Alarm button" diff --git a/html/changelogs/AutoChangeLog-pr-13972.yml b/html/changelogs/AutoChangeLog-pr-13972.yml deleted file mode 100644 index fdfdd4786d..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13972.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "MrJWhit" -delete-after: True -changes: - - tweak: "Fixes maint area in boxstation" diff --git a/html/changelogs/AutoChangeLog-pr-13975.yml b/html/changelogs/AutoChangeLog-pr-13975.yml deleted file mode 100644 index ff643cd19c..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13975.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "zeroisthebiggay" -delete-after: True -changes: - - imageadd: "fuck the r*d cr*ss" diff --git a/html/changelogs/AutoChangeLog-pr-13976.yml b/html/changelogs/AutoChangeLog-pr-13976.yml deleted file mode 100644 index dac85f8e9a..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13976.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "timothyteakettle" -delete-after: True -changes: - - rscadd: "you can teleport bread" diff --git a/html/changelogs/AutoChangeLog-pr-13978.yml b/html/changelogs/AutoChangeLog-pr-13978.yml deleted file mode 100644 index 94c19edec4..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13978.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "silicons" -delete-after: True -changes: - - balance: "c4 can no longer gib mobs" diff --git a/html/changelogs/AutoChangeLog-pr-13980.yml b/html/changelogs/AutoChangeLog-pr-13980.yml deleted file mode 100644 index 6838ba48a2..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13980.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "MrJWhit" -delete-after: True -changes: - - rscadd: "Makes gas sensors fireproof." diff --git a/html/changelogs/AutoChangeLog-pr-13983.yml b/html/changelogs/AutoChangeLog-pr-13983.yml deleted file mode 100644 index ba0bf62e6a..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13983.yml +++ /dev/null @@ -1,6 +0,0 @@ -author: "shellspeed1" -delete-after: True -changes: - - rscadd: "Xenomorph hybrids can now select wings -~~add: Xenomorph hybrids can now speak xenomorph~~" - - rscadd: "Xenomorph tongues are available for customization." diff --git a/html/changelogs/AutoChangeLog-pr-13988.yml b/html/changelogs/AutoChangeLog-pr-13988.yml deleted file mode 100644 index 262e5f804f..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13988.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "DeltaFire15" -delete-after: True -changes: - - bugfix: "Cargo packs marked as 'no private buying' now actually register as such." diff --git a/html/changelogs/AutoChangeLog-pr-13993.yml b/html/changelogs/AutoChangeLog-pr-13993.yml deleted file mode 100644 index d99ed0e0d1..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13993.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Arturlang" -delete-after: True -changes: - - tweak: "The cursed heart now only takes away half as much blood every loop, and can be used as long as you are alive, instead if only you are awake/able to use your hands" diff --git a/html/changelogs/AutoChangeLog-pr-13995.yml b/html/changelogs/AutoChangeLog-pr-13995.yml deleted file mode 100644 index e58ee3103c..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13995.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "DeltaFire15" -delete-after: True -changes: - - bugfix: "Attacking some certain objects no longer has no clickdelay." diff --git a/html/changelogs/AutoChangeLog-pr-13996.yml b/html/changelogs/AutoChangeLog-pr-13996.yml deleted file mode 100644 index 5bea075460..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13996.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "MrJWhit" -delete-after: True -changes: - - tweak: "Bugfix of a morph becoming an AI eye" diff --git a/html/changelogs/AutoChangeLog-pr-13997.yml b/html/changelogs/AutoChangeLog-pr-13997.yml deleted file mode 100644 index 354f66010a..0000000000 --- a/html/changelogs/AutoChangeLog-pr-13997.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "MrJWhit" -delete-after: True -changes: - - tweak: "Porting garbage collection tweak from /tg/" diff --git a/html/changelogs/AutoChangeLog-pr-14001.yml b/html/changelogs/AutoChangeLog-pr-14001.yml deleted file mode 100644 index 31c9e560ab..0000000000 --- a/html/changelogs/AutoChangeLog-pr-14001.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "MrJWhit" -delete-after: True -changes: - - tweak: "Fixes two chairs on one table" diff --git a/html/changelogs/AutoChangeLog-pr-14005.yml b/html/changelogs/AutoChangeLog-pr-14005.yml deleted file mode 100644 index 253f4e137a..0000000000 --- a/html/changelogs/AutoChangeLog-pr-14005.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "DeltaFire15" -delete-after: True -changes: - - bugfix: "Loot items mobs drop are no longer always failing to initialize." diff --git a/html/changelogs/AutoChangeLog-pr-14006.yml b/html/changelogs/AutoChangeLog-pr-14006.yml deleted file mode 100644 index 519ad32752..0000000000 --- a/html/changelogs/AutoChangeLog-pr-14006.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "DeltaFire15" -delete-after: True -changes: - - bugfix: "Cyborg B.o.r.i.s. installation now checks for if the chest has a cell, just like how it does with MMIs." diff --git a/html/changelogs/AutoChangeLog-pr-14007.yml b/html/changelogs/AutoChangeLog-pr-14007.yml deleted file mode 100644 index 498aa6b807..0000000000 --- a/html/changelogs/AutoChangeLog-pr-14007.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "DeltaFire15" -delete-after: True -changes: - - bugfix: "Antagging / Deantagging Heretics now properly sets their special role." diff --git a/html/changelogs/AutoChangeLog-pr-14008.yml b/html/changelogs/AutoChangeLog-pr-14008.yml deleted file mode 100644 index 836eefd99a..0000000000 --- a/html/changelogs/AutoChangeLog-pr-14008.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "TheSpaghetti" -delete-after: True -changes: - - bugfix: "no more tumor bread double punctuation" diff --git a/html/changelogs/AutoChangeLog-pr-14009.yml b/html/changelogs/AutoChangeLog-pr-14009.yml deleted file mode 100644 index 03a1ca39dd..0000000000 --- a/html/changelogs/AutoChangeLog-pr-14009.yml +++ /dev/null @@ -1,7 +0,0 @@ -author: "shellspeed1" -delete-after: True -changes: - - rscadd: "Wings from Cit RP have been ported over" - - rscadd: "Moth wings from cit have been ported over" - - bugfix: "Cleaned up some pixels on existing moth wings." - - tweak: "Organized the lists for wings by if they are for moths or not and than by alphabetical." diff --git a/html/changelogs/AutoChangeLog-pr-14010.yml b/html/changelogs/AutoChangeLog-pr-14010.yml deleted file mode 100644 index 643f28dc99..0000000000 --- a/html/changelogs/AutoChangeLog-pr-14010.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "shellspeed1" -delete-after: True -changes: - - rscadd: "The exofab can now print prosthetic limbs" - - bugfix: "The exofab was missing access to multiple cybernetic organs. This has now been rectified." diff --git a/html/changelogs/AutoChangeLog-pr-14014.yml b/html/changelogs/AutoChangeLog-pr-14014.yml deleted file mode 100644 index ac29b1567b..0000000000 --- a/html/changelogs/AutoChangeLog-pr-14014.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "necromanceranne" -delete-after: True -changes: - - bugfix: "Fixes various sprites for bokken, as well as being unable to craft certain parts and duplicate entries." diff --git a/html/changelogs/AutoChangeLog-pr-14015.yml b/html/changelogs/AutoChangeLog-pr-14015.yml deleted file mode 100644 index fe507d0a1e..0000000000 --- a/html/changelogs/AutoChangeLog-pr-14015.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "DeltaFire15" -delete-after: True -changes: - - balance: "Some synth damage stuff has been a bit rebalanced, see the PR for details." diff --git a/html/changelogs/AutoChangeLog-pr-14016.yml b/html/changelogs/AutoChangeLog-pr-14016.yml deleted file mode 100644 index 1aab587190..0000000000 --- a/html/changelogs/AutoChangeLog-pr-14016.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "timothyteakettle" -delete-after: True -changes: - - bugfix: "disabling adminhelp noises no longer disables looc" diff --git a/html/changelogs/AutoChangeLog-pr-14019.yml b/html/changelogs/AutoChangeLog-pr-14019.yml deleted file mode 100644 index 37bdd52948..0000000000 --- a/html/changelogs/AutoChangeLog-pr-14019.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "timothyteakettle" -delete-after: True -changes: - - bugfix: "species with NOTRANSSTING cannot have envy's knife used on them" diff --git a/html/changelogs/AutoChangeLog-pr-14021.yml b/html/changelogs/AutoChangeLog-pr-14021.yml deleted file mode 100644 index aab94ae724..0000000000 --- a/html/changelogs/AutoChangeLog-pr-14021.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "DeltaFire15" -delete-after: True -changes: - - bugfix: "Combat mode now will not stay permanently disabled due to status effects not working as intended." diff --git a/html/changelogs/AutoChangeLog-pr-14025.yml b/html/changelogs/AutoChangeLog-pr-14025.yml deleted file mode 100644 index 6db185baa3..0000000000 --- a/html/changelogs/AutoChangeLog-pr-14025.yml +++ /dev/null @@ -1,7 +0,0 @@ -author: "DeltaFire15" -delete-after: True -changes: - - rscadd: "A new surgery, allowing revival of synths without a defib at hand." - - balance: "Semi-permanent damage of Synth limbs caused by passing the damage threshold: 10 <- 15." - - tweak: "The embed removal surgery now has a version for Synths." - - balance: "EMPs no longer hardstun Synths." diff --git a/html/changelogs/AutoChangeLog-pr-14026.yml b/html/changelogs/AutoChangeLog-pr-14026.yml deleted file mode 100644 index 1ae9cb5c28..0000000000 --- a/html/changelogs/AutoChangeLog-pr-14026.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "BlueWildrose" -delete-after: True -changes: - - bugfix: "Fixes noodle size appearance for 12+ inch members." diff --git a/html/changelogs/AutoChangeLog-pr-14032.yml b/html/changelogs/AutoChangeLog-pr-14032.yml deleted file mode 100644 index 67f0a80f83..0000000000 --- a/html/changelogs/AutoChangeLog-pr-14032.yml +++ /dev/null @@ -1,8 +0,0 @@ -author: "kappa-sama" -delete-after: True -changes: - - rscadd: "A new spell for the wizard and his martial apprentices, the Inner Mantra technique. It makes you punch people really good and makes you durable, but drains your energy while it's active." - - rscadd: "A self-buffing spell for valiant bubblegum slayers that is ultimately useless on lavaland and probably overpowered for miner antagonists. Go figure. At least all it does is let you punch hard while draining your health every second." - - balance: "bubblegum now drops a book that makes you into an abusive father instead of a shotgun that plays like pre-nerf shotguns" - - soundadd: "a powerup and powerdown sound effect" - - imageadd: "two icons for two buff spells" diff --git a/html/changelogs/AutoChangeLog-pr-14033.yml b/html/changelogs/AutoChangeLog-pr-14033.yml deleted file mode 100644 index db6a6b0691..0000000000 --- a/html/changelogs/AutoChangeLog-pr-14033.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "BlueWildrose" -delete-after: True -changes: - - rscadd: "Color Mates have been added to all stations (except Snaxi). Enjoy coloring your attire without having to bug science!" diff --git a/html/changelogs/AutoChangeLog-pr-14034.yml b/html/changelogs/AutoChangeLog-pr-14034.yml deleted file mode 100644 index ecd0d4ca10..0000000000 --- a/html/changelogs/AutoChangeLog-pr-14034.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "DeltaFire15" -delete-after: True -changes: - - bugfix: "Some edge cases causing issues with system corruption shouldn't be able to occur anymore." diff --git a/html/changelogs/AutoChangeLog-pr-14035.yml b/html/changelogs/AutoChangeLog-pr-14035.yml deleted file mode 100644 index b8690368a3..0000000000 --- a/html/changelogs/AutoChangeLog-pr-14035.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "SiliconMain" -delete-after: True -changes: - - tweak: "hololocks (which haven't worked for god knows how long) commented out until auxmos is merged" diff --git a/html/changelogs/AutoChangeLog-pr-14036.yml b/html/changelogs/AutoChangeLog-pr-14036.yml deleted file mode 100644 index a2bb654233..0000000000 --- a/html/changelogs/AutoChangeLog-pr-14036.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "BlueWildrose" -delete-after: True -changes: - - tweak: "You will now only unbuckle fireman-carried/piggybacked people on disarm or harm intent." diff --git a/html/changelogs/AutoChangeLog-pr-14038.yml b/html/changelogs/AutoChangeLog-pr-14038.yml deleted file mode 100644 index 906f6f7053..0000000000 --- a/html/changelogs/AutoChangeLog-pr-14038.yml +++ /dev/null @@ -1,9 +0,0 @@ -author: "Hatterhat" -delete-after: True -changes: - - rscadd: "The nature interaction shuttle with the monkeys now has tiny fans on the airlocks in, because that's apparently a feature that was missing." - - rscadd: "More bags have been added to department vendors." - - balance: "Every roundstart species (and also ash walkers) now has flesh and bone that can be wounded." - - balance: "Recipes for sutures, regen mesh, and sterilized gauze have been adjusted to be easier, mostly." - - balance: "Sterilized gauze is better at absorbing blood and being a splint." - - bugfix: "Energy sabres now have an off inhand." diff --git a/html/changelogs/AutoChangeLog-pr-14039.yml b/html/changelogs/AutoChangeLog-pr-14039.yml deleted file mode 100644 index a799003a02..0000000000 --- a/html/changelogs/AutoChangeLog-pr-14039.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Hatterhat" -delete-after: True -changes: - - rscadd: "Basic sticky technology is now a roundstart tech. Advanced sticky technology is BEPIS-locked, though. Theoretically." diff --git a/html/changelogs/AutoChangeLog-pr-14040.yml b/html/changelogs/AutoChangeLog-pr-14040.yml deleted file mode 100644 index 6202f0b3e1..0000000000 --- a/html/changelogs/AutoChangeLog-pr-14040.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "LetterN" -delete-after: True -changes: - - bugfix: "fixed telecomms pda log" diff --git a/html/changelogs/AutoChangeLog-pr-14042.yml b/html/changelogs/AutoChangeLog-pr-14042.yml deleted file mode 100644 index a924ff582f..0000000000 --- a/html/changelogs/AutoChangeLog-pr-14042.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "kiwedespars" -delete-after: True -changes: - - rscadd: "the robust dildo weapon now has sound." diff --git a/html/changelogs/AutoChangeLog-pr-14044.yml b/html/changelogs/AutoChangeLog-pr-14044.yml deleted file mode 100644 index e19b6266a7..0000000000 --- a/html/changelogs/AutoChangeLog-pr-14044.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "corin9090" -delete-after: True -changes: - - tweak: "The chaplain's prayer beads can now be worn on your belt slot" diff --git a/html/changelogs/AutoChangeLog-pr-14048.yml b/html/changelogs/AutoChangeLog-pr-14048.yml deleted file mode 100644 index b7b1448b37..0000000000 --- a/html/changelogs/AutoChangeLog-pr-14048.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "MrJWhit" -delete-after: True -changes: - - tweak: "Updates suit storage info on Tip Of the Round." diff --git a/html/changelogs/AutoChangeLog-pr-14054.yml b/html/changelogs/AutoChangeLog-pr-14054.yml deleted file mode 100644 index 406e6b2713..0000000000 --- a/html/changelogs/AutoChangeLog-pr-14054.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "TheObserver" -delete-after: True -changes: - - rscadd: "Re-adds the rifle stock, and sets the improv shotgun to be as it was." - - rscdel: "The maintenance rifle has been shelved - for now. Watch this space." diff --git a/html/changelogs/AutoChangeLog-pr-14060.yml b/html/changelogs/AutoChangeLog-pr-14060.yml deleted file mode 100644 index f9750fc9be..0000000000 --- a/html/changelogs/AutoChangeLog-pr-14060.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Hatterhat" -delete-after: True -changes: - - rscdel: "Apparently, shrink rays were buyable again, despite a PR having been made a while ago specifically for removing shrink rays. They're gone again." diff --git a/html/changelogs/AutoChangeLog-pr-14062.yml b/html/changelogs/AutoChangeLog-pr-14062.yml deleted file mode 100644 index 84e40850ec..0000000000 --- a/html/changelogs/AutoChangeLog-pr-14062.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "kappa-sama" -delete-after: True -changes: - - bugfix: "super saiyan" diff --git a/html/changelogs/AutoChangeLog-pr-13526.yml b/html/changelogs/AutoChangeLog-pr-14423.yml similarity index 59% rename from html/changelogs/AutoChangeLog-pr-13526.yml rename to html/changelogs/AutoChangeLog-pr-14423.yml index 104b76bd94..db47b01d28 100644 --- a/html/changelogs/AutoChangeLog-pr-13526.yml +++ b/html/changelogs/AutoChangeLog-pr-14423.yml @@ -1,4 +1,4 @@ author: "Putnam3145" delete-after: True changes: - - bugfix: "vore prefs save now" + - bugfix: "chaos loads now" diff --git a/html/changelogs/example.yml b/html/changelogs/example.yml index 48aa13aa11..c44f796755 100644 --- a/html/changelogs/example.yml +++ b/html/changelogs/example.yml @@ -8,16 +8,41 @@ # # Valid Prefixes: # bugfix -# wip (For works in progress) +# - (fixes bugs) +# wip +# - (work in progress) # tweak +# - (tweaks something) # soundadd +# - (adds a sound) # sounddel -# rscadd (general adding of nice things) -# rscdel (general deleting of nice things) +# - (removes a sound) +# rscdel +# - (adds a feature) +# rscadd +# - (removes a feature) # imageadd +# - (adds an image or sprite) # imagedel -# spellcheck (typo fixes) +# - (removes an image or sprite) +# spellcheck +# - (fixes spelling or grammar) # experiment +# - (experimental change) +# tgs +# - (TGS change) +# balance +# - (balance changes) +# code_imp +# - (misc internal code change) +# refactor +# - (refactors code) +# config +# - (makes a change to the config files) +# admin +# - (makes changes to administrator tools) +# server +# - (miscellaneous changes to server) ################################# # Your name. diff --git a/html/oracle_ui/content/disposal_bin/index.html b/html/oracle_ui/content/disposal_bin/index.html deleted file mode 100644 index 8f7713b53c..0000000000 --- a/html/oracle_ui/content/disposal_bin/index.html +++ /dev/null @@ -1,27 +0,0 @@ -
    -
    - State: -
    @{full_pressure}
    -
    -
    - Pressure: -
    -
    -
    -
    @{per}
    -
    -
    -
    -
    - Handle: -
    @{flush}
    -
    -
    - Eject: -
    @{contents}
    -
    -
    - Compressor: -
    @{pressure_charging}
    -
    -
    \ No newline at end of file diff --git a/html/oracle_ui/editor_tool.html b/html/oracle_ui/editor_tool.html deleted file mode 100644 index e0ce75bb29..0000000000 --- a/html/oracle_ui/editor_tool.html +++ /dev/null @@ -1,103 +0,0 @@ - - - - - - OracleUI IDE - - - -
    -

    Content Template:

    - -
    -
    -

    Data:

    - -
    -
    -

    Output:

    - -
    - - - diff --git a/html/oracle_ui/themes/nano/index.html b/html/oracle_ui/themes/nano/index.html deleted file mode 100644 index 388f6e4ce4..0000000000 --- a/html/oracle_ui/themes/nano/index.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - @{title} - - - - - -
    -
    @{title}
    -
    - @{body} -
    -
    - - diff --git a/html/oracle_ui/themes/nano/sui-nano-common.css b/html/oracle_ui/themes/nano/sui-nano-common.css deleted file mode 100644 index 481b81c3e3..0000000000 --- a/html/oracle_ui/themes/nano/sui-nano-common.css +++ /dev/null @@ -1,353 +0,0 @@ -body -{ - padding: 0; - margin: 0; - background-color: #272727; - font-size: 12px; - color: #ffffff; - line-height: 170%; - cursor: default; - -moz-user-select: none; - -ms-user-select: none; -} - -hr -{ - background-color: #40628a; - height: 1px; -} - -a, a:link, a:visited, a:active, .linkOn, .linkOff -{ - color: #ffffff; - text-decoration: none; - background: #40628a; - border: 1px solid #161616; - padding: 1px 4px 1px 4px; - margin: 0 2px 0 0; - cursor:default; -} - -a:hover -{ - color: #40628a; - background: #ffffff; -} - -a.white, a.white:link, a.white:visited, a.white:active -{ - color: #40628a; - text-decoration: none; - background: #ffffff; - border: 1px solid #161616; - padding: 1px 4px 1px 4px; - margin: 0 2px 0 0; - cursor:default; -} - -a.white:hover -{ - color: #ffffff; - background: #40628a; -} - -.active, a.active:link, a.active:visited, a.active:active, a.active:hover -{ - color: #ffffff; - background: #2f943c; - border-color: #24722e; -} - -.disabled, a.disabled:link, a.disabled:visited, a.disabled:active, a.disabled:hover -{ - color: #ffffff; - background: #999999; - border-color: #666666; -} - -a.icon, .linkOn.icon, .linkOff.icon -{ - position: relative; - padding: 1px 4px 2px 20px; -} - -a.icon img, .linkOn.icon img -{ - position: absolute; - top: 0; - left: 0; - width: 18px; - height: 18px; -} - -ul -{ - padding: 4px 0 0 10px; - margin: 0; - list-style-type: none; -} - -li -{ - padding: 0 0 2px 0; -} - -img, a img -{ - border-style:none; -} - -h1, h2, h3, h4, h5, h6 -{ - margin: 0; - padding: 16px 0 8px 0; - color: #517087; -} - -h1 -{ - font-size: 15px; -} - -h2 -{ - font-size: 14px; -} - -h3 -{ - font-size: 13px; -} - -h4 -{ - font-size: 12px; -} - -.uiWrapper -{ - - width: 100%; - height: 100%; -} - -.uiTitle -{ - clear: both; - padding: 6px 8px 6px 8px; - border-bottom: 2px solid #161616; - background: #383838; - color: #98B0C3; - font-size: 16px; -} - -.uiTitle.icon -{ - padding: 6px 8px 6px 42px; - background-position: 2px 50%; - background-repeat: no-repeat; -} - -.uiContent -{ - clear: both; - padding: 8px; - font-family: Verdana, Geneva, sans-serif; -} - -.good -{ - color: #00ff00; -} - -.average -{ - color: #d09000; -} - -.bad -{ - color: #ff0000; -} - -.highlight -{ - color: #8BA5C4; -} - -.dark -{ - color: #272727; -} - -.notice -{ - position: relative; - background: #E9C183; - color: #15345A; - font-size: 10px; - font-style: italic; - padding: 2px 4px 0 4px; - margin: 4px; -} - -.notice.icon -{ - padding: 2px 4px 0 20px; -} - -.notice img -{ - position: absolute; - top: 0; - left: 0; - width: 16px; - height: 16px; -} - -div.notice -{ - clear: both; -} - -.statusDisplay -{ - background: #000000; - color: #ffffff; - border: 1px solid #40628a; - padding: 4px; - margin: 3px 0; -} - -.statusLabel -{ - width: 138px; - float: left; - overflow: hidden; - color: #98B0C3; -} - -.statusValue -{ - float: left; -} - -.block -{ - padding: 8px; - margin: 10px 4px 4px 4px; - border: 1px solid #40628a; - background-color: #202020; -} - -.block h3 -{ - padding: 0; -} - -.progressBar -{ - position: relative; - width: 185px; - height: 14px; - border: 1px solid #666666; - float: left; - overflow: hidden; - padding: 1px; -} - -.progressLabel -{ - top: -2px; - height: 100%; - position: absolute; - right: 4px; - text-align: right; -} - -.progressFill -{ - width: 100%; - height: 100%; - background: #40628a; - overflow: hidden; - transition: width 2.2s linear; -} - -.progressFill.good -{ - color: #ffffff; - background: #00ff00; -} - -.progressFill.average -{ - color: #ffffff; - background: #d09000; -} - -.progressFill.bad -{ - color: #ffffff; - background: #ff0000; -} - -.progressFill.highlight -{ - color: #ffffff; - background: #8BA5C4; -} - -.clearBoth -{ - clear: both; -} - -.clearLeft -{ - clear: left; -} - -.clearRight -{ - clear: right; -} - -.line -{ - width: 100%; - clear: both; -} - -section .label, section .content -{ - display: table-cell; - margin: 0; - text-align: left; - vertical-align: middle; - padding: 3px 2px -} - -section .label -{ - width: 1%; - padding-right: 32px; - white-space: nowrap; - color: #8ba5c4; -} - -section -{ - display: table-row; - width: 100% -} - -.display { - width: calc(100% - 8px); - padding: 4px; - background-color: #000; - background-color: rgba(0, 0, 0, .33); - box-shadow: inset 0 0 5px rgba(0, 0, 0, .5); - -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr=#54000000,endColorStr=#54000000)"; - filter: progid: DXImageTransform.Microsoft.gradient(startColorStr=#54000000, endColorStr=#54000000); -} \ No newline at end of file diff --git a/html/oracle_ui/themes/nano/sui-nano-common.js b/html/oracle_ui/themes/nano/sui-nano-common.js deleted file mode 100644 index 716891a53f..0000000000 --- a/html/oracle_ui/themes/nano/sui-nano-common.js +++ /dev/null @@ -1,47 +0,0 @@ -function replaceContent(body) { - var maincontent = document.getElementById('maincontent'); - if(maincontent) { - maincontent.innerHTML = body; - } -} - -function updateProgressLabels() { - var progressBars = document.getElementsByClassName("progressBar"); - for(var i = 0; i < progressBars.length; i++) { - var progressBar = progressBars[i]; - if(!progressBar) - continue; - var progressFill = progressBar.getElementsByClassName("progressFill")[0]; - if(!progressFill) - continue; - var width = parseInt(getComputedStyle(progressFill).width); - var maxWidth = parseInt(getComputedStyle(progressBar).width); - var progressLabel = progressBar.getElementsByClassName("progressLabel")[0]; - if(progressLabel) - progressLabel.innerHTML = Math.round((width / maxWidth) * 100) + '%'; - } -} - -if(getComputedStyle) { setInterval(updateProgressLabels, 50); } //Fallback - -function updateFields(json) { - var fields = JSON.parse(json); - for (var key in fields) { - let value = fields[key]; - var element = document.getElementById(key); - if(element == null) { - continue; - } else if(element.classList.contains('progressBar')) { - var progressFill = element.getElementsByClassName("progressFill")[0]; - if(progressFill) - progressFill.style["width"] = value; - if(!getComputedStyle) { //Fallback - var progressLabel = element.getElementsByClassName("progressLabel")[0]; - if(progressLabel) - progressLabel.innerHTML = value; - } - } else { - element.innerHTML = value; - } - } -} \ No newline at end of file diff --git a/html/oracle_ui/themes/nano/sui-nano-jquery.min.js b/html/oracle_ui/themes/nano/sui-nano-jquery.min.js deleted file mode 100644 index 645c5adc18..0000000000 --- a/html/oracle_ui/themes/nano/sui-nano-jquery.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery v1.11.1 | (c) 2005, 2014 jQuery Foundation, Inc. | jquery.org/license */ -!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l="1.11.1",m=function(a,b){return new m.fn.init(a,b)},n=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,o=/^-ms-/,p=/-([\da-z])/gi,q=function(a,b){return b.toUpperCase()};m.fn=m.prototype={jquery:l,constructor:m,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=m.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return m.each(this,a,b)},map:function(a){return this.pushStack(m.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},m.extend=m.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||m.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(e=arguments[h]))for(d in e)a=g[d],c=e[d],g!==c&&(j&&c&&(m.isPlainObject(c)||(b=m.isArray(c)))?(b?(b=!1,f=a&&m.isArray(a)?a:[]):f=a&&m.isPlainObject(a)?a:{},g[d]=m.extend(j,f,c)):void 0!==c&&(g[d]=c));return g},m.extend({expando:"jQuery"+(l+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===m.type(a)},isArray:Array.isArray||function(a){return"array"===m.type(a)},isWindow:function(a){return null!=a&&a==a.window},isNumeric:function(a){return!m.isArray(a)&&a-parseFloat(a)>=0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},isPlainObject:function(a){var b;if(!a||"object"!==m.type(a)||a.nodeType||m.isWindow(a))return!1;try{if(a.constructor&&!j.call(a,"constructor")&&!j.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}if(k.ownLast)for(b in a)return j.call(a,b);for(b in a);return void 0===b||j.call(a,b)},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(b){b&&m.trim(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(o,"ms-").replace(p,q)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=r(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(n,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(r(Object(a))?m.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){var d;if(b){if(g)return g.call(b,a,c);for(d=b.length,c=c?0>c?Math.max(0,d+c):c:0;d>c;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,b){var c=+b.length,d=0,e=a.length;while(c>d)a[e++]=b[d++];if(c!==c)while(void 0!==b[d])a[e++]=b[d++];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=r(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(f=a[b],b=a,a=f),m.isFunction(a)?(c=d.call(arguments,2),e=function(){return a.apply(b||this,c.concat(d.call(arguments)))},e.guid=a.guid=a.guid||m.guid++,e):void 0},now:function(){return+new Date},support:k}),m.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function r(a){var b=a.length,c=m.type(a);return"function"===c||m.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var s=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+-new Date,v=a.document,w=0,x=0,y=gb(),z=gb(),A=gb(),B=function(a,b){return a===b&&(l=!0),0},C="undefined",D=1<<31,E={}.hasOwnProperty,F=[],G=F.pop,H=F.push,I=F.push,J=F.slice,K=F.indexOf||function(a){for(var b=0,c=this.length;c>b;b++)if(this[b]===a)return b;return-1},L="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",M="[\\x20\\t\\r\\n\\f]",N="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",O=N.replace("w","w#"),P="\\["+M+"*("+N+")(?:"+M+"*([*^$|!~]?=)"+M+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+O+"))|)"+M+"*\\]",Q=":("+N+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+P+")*)|.*)\\)|)",R=new RegExp("^"+M+"+|((?:^|[^\\\\])(?:\\\\.)*)"+M+"+$","g"),S=new RegExp("^"+M+"*,"+M+"*"),T=new RegExp("^"+M+"*([>+~]|"+M+")"+M+"*"),U=new RegExp("="+M+"*([^\\]'\"]*?)"+M+"*\\]","g"),V=new RegExp(Q),W=new RegExp("^"+O+"$"),X={ID:new RegExp("^#("+N+")"),CLASS:new RegExp("^\\.("+N+")"),TAG:new RegExp("^("+N.replace("w","w*")+")"),ATTR:new RegExp("^"+P),PSEUDO:new RegExp("^"+Q),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+L+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,$=/^[^{]+\{\s*\[native \w/,_=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ab=/[+~]/,bb=/'|\\/g,cb=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),db=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)};try{I.apply(F=J.call(v.childNodes),v.childNodes),F[v.childNodes.length].nodeType}catch(eb){I={apply:F.length?function(a,b){H.apply(a,J.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function fb(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,d=d||[],!a||"string"!=typeof a)return d;if(1!==(k=b.nodeType)&&9!==k)return[];if(p&&!e){if(f=_.exec(a))if(j=f[1]){if(9===k){if(h=b.getElementById(j),!h||!h.parentNode)return d;if(h.id===j)return d.push(h),d}else if(b.ownerDocument&&(h=b.ownerDocument.getElementById(j))&&t(b,h)&&h.id===j)return d.push(h),d}else{if(f[2])return I.apply(d,b.getElementsByTagName(a)),d;if((j=f[3])&&c.getElementsByClassName&&b.getElementsByClassName)return I.apply(d,b.getElementsByClassName(j)),d}if(c.qsa&&(!q||!q.test(a))){if(s=r=u,w=b,x=9===k&&a,1===k&&"object"!==b.nodeName.toLowerCase()){o=g(a),(r=b.getAttribute("id"))?s=r.replace(bb,"\\$&"):b.setAttribute("id",s),s="[id='"+s+"'] ",l=o.length;while(l--)o[l]=s+qb(o[l]);w=ab.test(a)&&ob(b.parentNode)||b,x=o.join(",")}if(x)try{return I.apply(d,w.querySelectorAll(x)),d}catch(y){}finally{r||b.removeAttribute("id")}}}return i(a.replace(R,"$1"),b,d,e)}function gb(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function hb(a){return a[u]=!0,a}function ib(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function jb(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function kb(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||D)-(~a.sourceIndex||D);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function lb(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function mb(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function nb(a){return hb(function(b){return b=+b,hb(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function ob(a){return a&&typeof a.getElementsByTagName!==C&&a}c=fb.support={},f=fb.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=fb.setDocument=function(a){var b,e=a?a.ownerDocument||a:v,g=e.defaultView;return e!==n&&9===e.nodeType&&e.documentElement?(n=e,o=e.documentElement,p=!f(e),g&&g!==g.top&&(g.addEventListener?g.addEventListener("unload",function(){m()},!1):g.attachEvent&&g.attachEvent("onunload",function(){m()})),c.attributes=ib(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ib(function(a){return a.appendChild(e.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=$.test(e.getElementsByClassName)&&ib(function(a){return a.innerHTML="
    ",a.firstChild.className="i",2===a.getElementsByClassName("i").length}),c.getById=ib(function(a){return o.appendChild(a).id=u,!e.getElementsByName||!e.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if(typeof b.getElementById!==C&&p){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){var c=typeof a.getAttributeNode!==C&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return typeof b.getElementsByTagName!==C?b.getElementsByTagName(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return typeof b.getElementsByClassName!==C&&p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=$.test(e.querySelectorAll))&&(ib(function(a){a.innerHTML="",a.querySelectorAll("[msallowclip^='']").length&&q.push("[*^$]="+M+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+M+"*(?:value|"+L+")"),a.querySelectorAll(":checked").length||q.push(":checked")}),ib(function(a){var b=e.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+M+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ib(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",Q)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===e||a.ownerDocument===v&&t(v,a)?-1:b===e||b.ownerDocument===v&&t(v,b)?1:k?K.call(k,a)-K.call(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,f=a.parentNode,g=b.parentNode,h=[a],i=[b];if(!f||!g)return a===e?-1:b===e?1:f?-1:g?1:k?K.call(k,a)-K.call(k,b):0;if(f===g)return kb(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?kb(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},e):n},fb.matches=function(a,b){return fb(a,null,null,b)},fb.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,"='$1']"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return fb(b,n,null,[a]).length>0},fb.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},fb.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&E.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},fb.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},fb.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=fb.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=fb.selectors={cacheLength:50,createPseudo:hb,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(cb,db),a[3]=(a[3]||a[4]||a[5]||"").replace(cb,db),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||fb.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&fb.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&V.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(cb,db).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+M+")"+a+"("+M+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||typeof a.getAttribute!==C&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=fb.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(s&&((l[u]||(l[u]={}))[a]=[w,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||fb.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?hb(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=K.call(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:hb(function(a){var b=[],c=[],d=h(a.replace(R,"$1"));return d[u]?hb(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),!c.pop()}}),has:hb(function(a){return function(b){return fb(a,b).length>0}}),contains:hb(function(a){return function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:hb(function(a){return W.test(a||"")||fb.error("unsupported lang: "+a),a=a.replace(cb,db).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Z.test(a.nodeName)},input:function(a){return Y.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:nb(function(){return[0]}),last:nb(function(a,b){return[b-1]}),eq:nb(function(a,b,c){return[0>c?c+b:c]}),even:nb(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:nb(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:nb(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:nb(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function rb(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function sb(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function tb(a,b,c){for(var d=0,e=b.length;e>d;d++)fb(a,b[d],c);return c}function ub(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function vb(a,b,c,d,e,f){return d&&!d[u]&&(d=vb(d)),e&&!e[u]&&(e=vb(e,f)),hb(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||tb(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:ub(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=ub(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?K.call(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=ub(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):I.apply(g,r)})}function wb(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=rb(function(a){return a===b},h,!0),l=rb(function(a){return K.call(b,a)>-1},h,!0),m=[function(a,c,d){return!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d))}];f>i;i++)if(c=d.relative[a[i].type])m=[rb(sb(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return vb(i>1&&sb(m),i>1&&qb(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(R,"$1"),c,e>i&&wb(a.slice(i,e)),f>e&&wb(a=a.slice(e)),f>e&&qb(a))}m.push(c)}return sb(m)}function xb(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q="0",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG("*",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=G.call(i));s=ub(s)}I.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&fb.uniqueSort(i)}return k&&(w=v,j=t),r};return c?hb(f):f}return h=fb.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=wb(b[c]),f[u]?d.push(f):e.push(f);f=A(a,xb(e,d)),f.selector=a}return f},i=fb.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(cb,db),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(cb,db),ab.test(j[0].type)&&ob(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&qb(j),!a)return I.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,ab.test(a)&&ob(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ib(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ib(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||jb("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ib(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||jb("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ib(function(a){return null==a.getAttribute("disabled")})||jb(L,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),fb}(a);m.find=s,m.expr=s.selectors,m.expr[":"]=m.expr.pseudos,m.unique=s.uniqueSort,m.text=s.getText,m.isXMLDoc=s.isXML,m.contains=s.contains;var t=m.expr.match.needsContext,u=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,v=/^.[^:#\[\.,]*$/;function w(a,b,c){if(m.isFunction(b))return m.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return m.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(v.test(b))return m.filter(b,a,c);b=m.filter(b,a)}return m.grep(a,function(a){return m.inArray(a,b)>=0!==c})}m.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?m.find.matchesSelector(d,a)?[d]:[]:m.find.matches(a,m.grep(b,function(a){return 1===a.nodeType}))},m.fn.extend({find:function(a){var b,c=[],d=this,e=d.length;if("string"!=typeof a)return this.pushStack(m(a).filter(function(){for(b=0;e>b;b++)if(m.contains(d[b],this))return!0}));for(b=0;e>b;b++)m.find(a,d[b],c);return c=this.pushStack(e>1?m.unique(c):c),c.selector=this.selector?this.selector+" "+a:a,c},filter:function(a){return this.pushStack(w(this,a||[],!1))},not:function(a){return this.pushStack(w(this,a||[],!0))},is:function(a){return!!w(this,"string"==typeof a&&t.test(a)?m(a):a||[],!1).length}});var x,y=a.document,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=m.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a.charAt(0)&&">"===a.charAt(a.length-1)&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||x).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof m?b[0]:b,m.merge(this,m.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:y,!0)),u.test(c[1])&&m.isPlainObject(b))for(c in b)m.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}if(d=y.getElementById(c[2]),d&&d.parentNode){if(d.id!==c[2])return x.find(a);this.length=1,this[0]=d}return this.context=y,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):m.isFunction(a)?"undefined"!=typeof x.ready?x.ready(a):a(m):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),m.makeArray(a,this))};A.prototype=m.fn,x=m(y);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};m.extend({dir:function(a,b,c){var d=[],e=a[b];while(e&&9!==e.nodeType&&(void 0===c||1!==e.nodeType||!m(e).is(c)))1===e.nodeType&&d.push(e),e=e[b];return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),m.fn.extend({has:function(a){var b,c=m(a,this),d=c.length;return this.filter(function(){for(b=0;d>b;b++)if(m.contains(this,c[b]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=t.test(a)||"string"!=typeof a?m(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&m.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?m.unique(f):f)},index:function(a){return a?"string"==typeof a?m.inArray(this[0],m(a)):m.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(m.unique(m.merge(this.get(),m(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){do a=a[b];while(a&&1!==a.nodeType);return a}m.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return m.dir(a,"parentNode")},parentsUntil:function(a,b,c){return m.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return m.dir(a,"nextSibling")},prevAll:function(a){return m.dir(a,"previousSibling")},nextUntil:function(a,b,c){return m.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return m.dir(a,"previousSibling",c)},siblings:function(a){return m.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return m.sibling(a.firstChild)},contents:function(a){return m.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:m.merge([],a.childNodes)}},function(a,b){m.fn[a]=function(c,d){var e=m.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=m.filter(d,e)),this.length>1&&(C[a]||(e=m.unique(e)),B.test(a)&&(e=e.reverse())),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return m.each(a.match(E)||[],function(a,c){b[c]=!0}),b}m.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):m.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(c=a.memory&&l,d=!0,f=g||0,g=0,e=h.length,b=!0;h&&e>f;f++)if(h[f].apply(l[0],l[1])===!1&&a.stopOnFalse){c=!1;break}b=!1,h&&(i?i.length&&j(i.shift()):c?h=[]:k.disable())},k={add:function(){if(h){var d=h.length;!function f(b){m.each(b,function(b,c){var d=m.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&f(c)})}(arguments),b?e=h.length:c&&(g=d,j(c))}return this},remove:function(){return h&&m.each(arguments,function(a,c){var d;while((d=m.inArray(c,h,d))>-1)h.splice(d,1),b&&(e>=d&&e--,f>=d&&f--)}),this},has:function(a){return a?m.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],e=0,this},disable:function(){return h=i=c=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,c||k.disable(),this},locked:function(){return!i},fireWith:function(a,c){return!h||d&&!i||(c=c||[],c=[a,c.slice?c.slice():c],b?i.push(c):j(c)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!d}};return k},m.extend({Deferred:function(a){var b=[["resolve","done",m.Callbacks("once memory"),"resolved"],["reject","fail",m.Callbacks("once memory"),"rejected"],["notify","progress",m.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return m.Deferred(function(c){m.each(b,function(b,f){var g=m.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&m.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?m.extend(a,d):d}},e={};return d.pipe=d.then,m.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&m.isFunction(a.promise)?e:0,g=1===f?a:m.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&m.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;m.fn.ready=function(a){return m.ready.promise().done(a),this},m.extend({isReady:!1,readyWait:1,holdReady:function(a){a?m.readyWait++:m.ready(!0)},ready:function(a){if(a===!0?!--m.readyWait:!m.isReady){if(!y.body)return setTimeout(m.ready);m.isReady=!0,a!==!0&&--m.readyWait>0||(H.resolveWith(y,[m]),m.fn.triggerHandler&&(m(y).triggerHandler("ready"),m(y).off("ready")))}}});function I(){y.addEventListener?(y.removeEventListener("DOMContentLoaded",J,!1),a.removeEventListener("load",J,!1)):(y.detachEvent("onreadystatechange",J),a.detachEvent("onload",J))}function J(){(y.addEventListener||"load"===event.type||"complete"===y.readyState)&&(I(),m.ready())}m.ready.promise=function(b){if(!H)if(H=m.Deferred(),"complete"===y.readyState)setTimeout(m.ready);else if(y.addEventListener)y.addEventListener("DOMContentLoaded",J,!1),a.addEventListener("load",J,!1);else{y.attachEvent("onreadystatechange",J),a.attachEvent("onload",J);var c=!1;try{c=null==a.frameElement&&y.documentElement}catch(d){}c&&c.doScroll&&!function e(){if(!m.isReady){try{c.doScroll("left")}catch(a){return setTimeout(e,50)}I(),m.ready()}}()}return H.promise(b)};var K="undefined",L;for(L in m(k))break;k.ownLast="0"!==L,k.inlineBlockNeedsLayout=!1,m(function(){var a,b,c,d;c=y.getElementsByTagName("body")[0],c&&c.style&&(b=y.createElement("div"),d=y.createElement("div"),d.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(d).appendChild(b),typeof b.style.zoom!==K&&(b.style.cssText="display:inline;margin:0;border:0;padding:1px;width:1px;zoom:1",k.inlineBlockNeedsLayout=a=3===b.offsetWidth,a&&(c.style.zoom=1)),c.removeChild(d))}),function(){var a=y.createElement("div");if(null==k.deleteExpando){k.deleteExpando=!0;try{delete a.test}catch(b){k.deleteExpando=!1}}a=null}(),m.acceptData=function(a){var b=m.noData[(a.nodeName+" ").toLowerCase()],c=+a.nodeType||1;return 1!==c&&9!==c?!1:!b||b!==!0&&a.getAttribute("classid")===b};var M=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,N=/([A-Z])/g;function O(a,b,c){if(void 0===c&&1===a.nodeType){var d="data-"+b.replace(N,"-$1").toLowerCase();if(c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:M.test(c)?m.parseJSON(c):c}catch(e){}m.data(a,b,c)}else c=void 0}return c}function P(a){var b;for(b in a)if(("data"!==b||!m.isEmptyObject(a[b]))&&"toJSON"!==b)return!1;return!0}function Q(a,b,d,e){if(m.acceptData(a)){var f,g,h=m.expando,i=a.nodeType,j=i?m.cache:a,k=i?a[h]:a[h]&&h; - if(k&&j[k]&&(e||j[k].data)||void 0!==d||"string"!=typeof b)return k||(k=i?a[h]=c.pop()||m.guid++:h),j[k]||(j[k]=i?{}:{toJSON:m.noop}),("object"==typeof b||"function"==typeof b)&&(e?j[k]=m.extend(j[k],b):j[k].data=m.extend(j[k].data,b)),g=j[k],e||(g.data||(g.data={}),g=g.data),void 0!==d&&(g[m.camelCase(b)]=d),"string"==typeof b?(f=g[b],null==f&&(f=g[m.camelCase(b)])):f=g,f}}function R(a,b,c){if(m.acceptData(a)){var d,e,f=a.nodeType,g=f?m.cache:a,h=f?a[m.expando]:m.expando;if(g[h]){if(b&&(d=c?g[h]:g[h].data)){m.isArray(b)?b=b.concat(m.map(b,m.camelCase)):b in d?b=[b]:(b=m.camelCase(b),b=b in d?[b]:b.split(" ")),e=b.length;while(e--)delete d[b[e]];if(c?!P(d):!m.isEmptyObject(d))return}(c||(delete g[h].data,P(g[h])))&&(f?m.cleanData([a],!0):k.deleteExpando||g!=g.window?delete g[h]:g[h]=null)}}}m.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(a){return a=a.nodeType?m.cache[a[m.expando]]:a[m.expando],!!a&&!P(a)},data:function(a,b,c){return Q(a,b,c)},removeData:function(a,b){return R(a,b)},_data:function(a,b,c){return Q(a,b,c,!0)},_removeData:function(a,b){return R(a,b,!0)}}),m.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=m.data(f),1===f.nodeType&&!m._data(f,"parsedAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=m.camelCase(d.slice(5)),O(f,d,e[d])));m._data(f,"parsedAttrs",!0)}return e}return"object"==typeof a?this.each(function(){m.data(this,a)}):arguments.length>1?this.each(function(){m.data(this,a,b)}):f?O(f,a,m.data(f,a)):void 0},removeData:function(a){return this.each(function(){m.removeData(this,a)})}}),m.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=m._data(a,b),c&&(!d||m.isArray(c)?d=m._data(a,b,m.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=m.queue(a,b),d=c.length,e=c.shift(),f=m._queueHooks(a,b),g=function(){m.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return m._data(a,c)||m._data(a,c,{empty:m.Callbacks("once memory").add(function(){m._removeData(a,b+"queue"),m._removeData(a,c)})})}}),m.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthh;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},W=/^(?:checkbox|radio)$/i;!function(){var a=y.createElement("input"),b=y.createElement("div"),c=y.createDocumentFragment();if(b.innerHTML="
    a",k.leadingWhitespace=3===b.firstChild.nodeType,k.tbody=!b.getElementsByTagName("tbody").length,k.htmlSerialize=!!b.getElementsByTagName("link").length,k.html5Clone="<:nav>"!==y.createElement("nav").cloneNode(!0).outerHTML,a.type="checkbox",a.checked=!0,c.appendChild(a),k.appendChecked=a.checked,b.innerHTML="",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue,c.appendChild(b),b.innerHTML="",k.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,k.noCloneEvent=!0,b.attachEvent&&(b.attachEvent("onclick",function(){k.noCloneEvent=!1}),b.cloneNode(!0).click()),null==k.deleteExpando){k.deleteExpando=!0;try{delete b.test}catch(d){k.deleteExpando=!1}}}(),function(){var b,c,d=y.createElement("div");for(b in{submit:!0,change:!0,focusin:!0})c="on"+b,(k[b+"Bubbles"]=c in a)||(d.setAttribute(c,"t"),k[b+"Bubbles"]=d.attributes[c].expando===!1);d=null}();var X=/^(?:input|select|textarea)$/i,Y=/^key/,Z=/^(?:mouse|pointer|contextmenu)|click/,$=/^(?:focusinfocus|focusoutblur)$/,_=/^([^.]*)(?:\.(.+)|)$/;function ab(){return!0}function bb(){return!1}function cb(){try{return y.activeElement}catch(a){}}m.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,n,o,p,q,r=m._data(a);if(r){c.handler&&(i=c,c=i.handler,e=i.selector),c.guid||(c.guid=m.guid++),(g=r.events)||(g=r.events={}),(k=r.handle)||(k=r.handle=function(a){return typeof m===K||a&&m.event.triggered===a.type?void 0:m.event.dispatch.apply(k.elem,arguments)},k.elem=a),b=(b||"").match(E)||[""],h=b.length;while(h--)f=_.exec(b[h])||[],o=q=f[1],p=(f[2]||"").split(".").sort(),o&&(j=m.event.special[o]||{},o=(e?j.delegateType:j.bindType)||o,j=m.event.special[o]||{},l=m.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&m.expr.match.needsContext.test(e),namespace:p.join(".")},i),(n=g[o])||(n=g[o]=[],n.delegateCount=0,j.setup&&j.setup.call(a,d,p,k)!==!1||(a.addEventListener?a.addEventListener(o,k,!1):a.attachEvent&&a.attachEvent("on"+o,k))),j.add&&(j.add.call(a,l),l.handler.guid||(l.handler.guid=c.guid)),e?n.splice(n.delegateCount++,0,l):n.push(l),m.event.global[o]=!0);a=null}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,n,o,p,q,r=m.hasData(a)&&m._data(a);if(r&&(k=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=_.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=m.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,n=k[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),i=f=n.length;while(f--)g=n[f],!e&&q!==g.origType||c&&c.guid!==g.guid||h&&!h.test(g.namespace)||d&&d!==g.selector&&("**"!==d||!g.selector)||(n.splice(f,1),g.selector&&n.delegateCount--,l.remove&&l.remove.call(a,g));i&&!n.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||m.removeEvent(a,o,r.handle),delete k[o])}else for(o in k)m.event.remove(a,o+b[j],c,d,!0);m.isEmptyObject(k)&&(delete r.handle,m._removeData(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,l,n,o=[d||y],p=j.call(b,"type")?b.type:b,q=j.call(b,"namespace")?b.namespace.split("."):[];if(h=l=d=d||y,3!==d.nodeType&&8!==d.nodeType&&!$.test(p+m.event.triggered)&&(p.indexOf(".")>=0&&(q=p.split("."),p=q.shift(),q.sort()),g=p.indexOf(":")<0&&"on"+p,b=b[m.expando]?b:new m.Event(p,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=q.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:m.makeArray(c,[b]),k=m.event.special[p]||{},e||!k.trigger||k.trigger.apply(d,c)!==!1)){if(!e&&!k.noBubble&&!m.isWindow(d)){for(i=k.delegateType||p,$.test(i+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),l=h;l===(d.ownerDocument||y)&&o.push(l.defaultView||l.parentWindow||a)}n=0;while((h=o[n++])&&!b.isPropagationStopped())b.type=n>1?i:k.bindType||p,f=(m._data(h,"events")||{})[b.type]&&m._data(h,"handle"),f&&f.apply(h,c),f=g&&h[g],f&&f.apply&&m.acceptData(h)&&(b.result=f.apply(h,c),b.result===!1&&b.preventDefault());if(b.type=p,!e&&!b.isDefaultPrevented()&&(!k._default||k._default.apply(o.pop(),c)===!1)&&m.acceptData(d)&&g&&d[p]&&!m.isWindow(d)){l=d[g],l&&(d[g]=null),m.event.triggered=p;try{d[p]()}catch(r){}m.event.triggered=void 0,l&&(d[g]=l)}return b.result}},dispatch:function(a){a=m.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(m._data(this,"events")||{})[a.type]||[],k=m.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=m.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,g=0;while((e=f.handlers[g++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(e.namespace))&&(a.handleObj=e,a.data=e.data,c=((m.event.special[e.origType]||{}).handle||e.handler).apply(f.elem,i),void 0!==c&&(a.result=c)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!=this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(e=[],f=0;h>f;f++)d=b[f],c=d.selector+" ",void 0===e[c]&&(e[c]=d.needsContext?m(c,this).index(i)>=0:m.find(c,this,null,[i]).length),e[c]&&e.push(d);e.length&&g.push({elem:i,handlers:e})}return h]","i"),hb=/^\s+/,ib=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,jb=/<([\w:]+)/,kb=/\s*$/g,rb={option:[1,""],legend:[1,"
    ","
    "],area:[1,"",""],param:[1,"",""],thead:[1,"","
    "],tr:[2,"","
    "],col:[2,"","
    "],td:[3,"","
    "],_default:k.htmlSerialize?[0,"",""]:[1,"X
    ","
    "]},sb=db(y),tb=sb.appendChild(y.createElement("div"));rb.optgroup=rb.option,rb.tbody=rb.tfoot=rb.colgroup=rb.caption=rb.thead,rb.th=rb.td;function ub(a,b){var c,d,e=0,f=typeof a.getElementsByTagName!==K?a.getElementsByTagName(b||"*"):typeof a.querySelectorAll!==K?a.querySelectorAll(b||"*"):void 0;if(!f)for(f=[],c=a.childNodes||a;null!=(d=c[e]);e++)!b||m.nodeName(d,b)?f.push(d):m.merge(f,ub(d,b));return void 0===b||b&&m.nodeName(a,b)?m.merge([a],f):f}function vb(a){W.test(a.type)&&(a.defaultChecked=a.checked)}function wb(a,b){return m.nodeName(a,"table")&&m.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function xb(a){return a.type=(null!==m.find.attr(a,"type"))+"/"+a.type,a}function yb(a){var b=pb.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function zb(a,b){for(var c,d=0;null!=(c=a[d]);d++)m._data(c,"globalEval",!b||m._data(b[d],"globalEval"))}function Ab(a,b){if(1===b.nodeType&&m.hasData(a)){var c,d,e,f=m._data(a),g=m._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;e>d;d++)m.event.add(b,c,h[c][d])}g.data&&(g.data=m.extend({},g.data))}}function Bb(a,b){var c,d,e;if(1===b.nodeType){if(c=b.nodeName.toLowerCase(),!k.noCloneEvent&&b[m.expando]){e=m._data(b);for(d in e.events)m.removeEvent(b,d,e.handle);b.removeAttribute(m.expando)}"script"===c&&b.text!==a.text?(xb(b).text=a.text,yb(b)):"object"===c?(b.parentNode&&(b.outerHTML=a.outerHTML),k.html5Clone&&a.innerHTML&&!m.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):"input"===c&&W.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):"option"===c?b.defaultSelected=b.selected=a.defaultSelected:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}}m.extend({clone:function(a,b,c){var d,e,f,g,h,i=m.contains(a.ownerDocument,a);if(k.html5Clone||m.isXMLDoc(a)||!gb.test("<"+a.nodeName+">")?f=a.cloneNode(!0):(tb.innerHTML=a.outerHTML,tb.removeChild(f=tb.firstChild)),!(k.noCloneEvent&&k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||m.isXMLDoc(a)))for(d=ub(f),h=ub(a),g=0;null!=(e=h[g]);++g)d[g]&&Bb(e,d[g]);if(b)if(c)for(h=h||ub(a),d=d||ub(f),g=0;null!=(e=h[g]);g++)Ab(e,d[g]);else Ab(a,f);return d=ub(f,"script"),d.length>0&&zb(d,!i&&ub(a,"script")),d=h=e=null,f},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,l,n=a.length,o=db(b),p=[],q=0;n>q;q++)if(f=a[q],f||0===f)if("object"===m.type(f))m.merge(p,f.nodeType?[f]:f);else if(lb.test(f)){h=h||o.appendChild(b.createElement("div")),i=(jb.exec(f)||["",""])[1].toLowerCase(),l=rb[i]||rb._default,h.innerHTML=l[1]+f.replace(ib,"<$1>")+l[2],e=l[0];while(e--)h=h.lastChild;if(!k.leadingWhitespace&&hb.test(f)&&p.push(b.createTextNode(hb.exec(f)[0])),!k.tbody){f="table"!==i||kb.test(f)?""!==l[1]||kb.test(f)?0:h:h.firstChild,e=f&&f.childNodes.length;while(e--)m.nodeName(j=f.childNodes[e],"tbody")&&!j.childNodes.length&&f.removeChild(j)}m.merge(p,h.childNodes),h.textContent="";while(h.firstChild)h.removeChild(h.firstChild);h=o.lastChild}else p.push(b.createTextNode(f));h&&o.removeChild(h),k.appendChecked||m.grep(ub(p,"input"),vb),q=0;while(f=p[q++])if((!d||-1===m.inArray(f,d))&&(g=m.contains(f.ownerDocument,f),h=ub(o.appendChild(f),"script"),g&&zb(h),c)){e=0;while(f=h[e++])ob.test(f.type||"")&&c.push(f)}return h=null,o},cleanData:function(a,b){for(var d,e,f,g,h=0,i=m.expando,j=m.cache,l=k.deleteExpando,n=m.event.special;null!=(d=a[h]);h++)if((b||m.acceptData(d))&&(f=d[i],g=f&&j[f])){if(g.events)for(e in g.events)n[e]?m.event.remove(d,e):m.removeEvent(d,e,g.handle);j[f]&&(delete j[f],l?delete d[i]:typeof d.removeAttribute!==K?d.removeAttribute(i):d[i]=null,c.push(f))}}}),m.fn.extend({text:function(a){return V(this,function(a){return void 0===a?m.text(this):this.empty().append((this[0]&&this[0].ownerDocument||y).createTextNode(a))},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=wb(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=wb(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?m.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||m.cleanData(ub(c)),c.parentNode&&(b&&m.contains(c.ownerDocument,c)&&zb(ub(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++){1===a.nodeType&&m.cleanData(ub(a,!1));while(a.firstChild)a.removeChild(a.firstChild);a.options&&m.nodeName(a,"select")&&(a.options.length=0)}return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return m.clone(this,a,b)})},html:function(a){return V(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a)return 1===b.nodeType?b.innerHTML.replace(fb,""):void 0;if(!("string"!=typeof a||mb.test(a)||!k.htmlSerialize&&gb.test(a)||!k.leadingWhitespace&&hb.test(a)||rb[(jb.exec(a)||["",""])[1].toLowerCase()])){a=a.replace(ib,"<$1>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(m.cleanData(ub(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,m.cleanData(ub(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,n=this,o=l-1,p=a[0],q=m.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&nb.test(p))return this.each(function(c){var d=n.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(i=m.buildFragment(a,this[0].ownerDocument,!1,this),c=i.firstChild,1===i.childNodes.length&&(i=c),c)){for(g=m.map(ub(i,"script"),xb),f=g.length;l>j;j++)d=i,j!==o&&(d=m.clone(d,!0,!0),f&&m.merge(g,ub(d,"script"))),b.call(this[j],d,j);if(f)for(h=g[g.length-1].ownerDocument,m.map(g,yb),j=0;f>j;j++)d=g[j],ob.test(d.type||"")&&!m._data(d,"globalEval")&&m.contains(h,d)&&(d.src?m._evalUrl&&m._evalUrl(d.src):m.globalEval((d.text||d.textContent||d.innerHTML||"").replace(qb,"")));i=c=null}return this}}),m.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){m.fn[a]=function(a){for(var c,d=0,e=[],g=m(a),h=g.length-1;h>=d;d++)c=d===h?this:this.clone(!0),m(g[d])[b](c),f.apply(e,c.get());return this.pushStack(e)}});var Cb,Db={};function Eb(b,c){var d,e=m(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:m.css(e[0],"display");return e.detach(),f}function Fb(a){var b=y,c=Db[a];return c||(c=Eb(a,b),"none"!==c&&c||(Cb=(Cb||m("