diff --git a/.dockerignore b/.dockerignore index 9c6ac6b4aa..a4b8a25cdc 100644 --- a/.dockerignore +++ b/.dockerignore @@ -22,7 +22,6 @@ tgstation.int tgstation.rsc tgstation.lk tgstation.dyn.rsc -libmariadb.dll -rust_g.dll -BSQL.dll +*.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 b23dfe6932..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 diff --git a/.github/workflows/ci_suite.yml b/.github/workflows/ci_suite.yml index 76a1c26656..a6b91bd559 100644 --- a/.github/workflows/ci_suite.yml +++ b/.github/workflows/ci_suite.yml @@ -10,7 +10,7 @@ jobs: run_linters: if: "!contains(github.event.head_commit.message, '[ci skip]')" name: Run Linters - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 - name: Setup cache @@ -21,18 +21,20 @@ jobs: - name: Install Tools run: | pip3 install setuptools - bash tools/ci/install_build_tools.sh + bash tools/ci/install_node.sh bash tools/ci/install_spaceman_dmm.sh dreamchecker - pip3 install -r tools/mapmerge2/requirements.txt + 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 - bash tools/ci/build_tgui.sh + tgui/bin/tgui --lint + tgui/bin/tgui --test bash tools/ci/check_grep.sh - python3 tools/mapmerge2/dmi.py --test + 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 @@ -43,7 +45,7 @@ jobs: compile_all_maps: if: "!contains(github.event.head_commit.message, '[ci skip]')" name: Compile Maps - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 - name: Setup cache @@ -56,11 +58,13 @@ jobs: 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-latest + runs-on: ubuntu-20.04 services: mysql: image: mysql:latest @@ -87,14 +91,16 @@ jobs: run: | sudo dpkg --add-architecture i386 sudo apt update || true - sudo apt install libssl1.1:i386 + 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 - # bash tools/ci/dm.sh -DCIBUILDING tgstation.dme - # bash tools/ci/run_server.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 diff --git a/.github/workflows/compile_changelogs.yml b/.github/workflows/compile_changelogs.yml index c8756f0d28..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 diff --git a/.github/workflows/docker_publish.yml b/.github/workflows/docker_publish.yml index 7417a382c4..32d160b840 100644 --- a/.github/workflows/docker_publish.yml +++ b/.github/workflows/docker_publish.yml @@ -7,7 +7,8 @@ on: jobs: publish: - runs-on: ubuntu-latest + if: "!contains(github.event.head_commit.message, '[ci skip]')" + runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/generate_documentation.yml b/.github/workflows/generate_documentation.yml index d0d61be073..c1b7a2d5e1 100644 --- a/.github/workflows/generate_documentation.yml +++ b/.github/workflows/generate_documentation.yml @@ -6,7 +6,7 @@ on: jobs: generate_documentation: if: "!contains(github.event.head_commit.message, '[ci skip]')" - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 - name: Setup cache 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 c7bb5c970c..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 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/.vscode/extensions.json b/.vscode/extensions.json index bf0d9d2fb9..d384c4535b 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -5,6 +5,7 @@ "EditorConfig.EditorConfig", "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", - "kevinkyang.auto-comment-blocks" + "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 cca7e43a54..0ae4f82be8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,62 +1,85 @@ -FROM tgstation/byond:513.1533 as base +# base = ubuntu + full apt update +FROM ubuntu:xenial AS base -FROM base as rust_g - -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 +# 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 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 \ - && rm /deploy/*.dll - -FROM dm_base - -EXPOSE 1337 - -RUN apt-get update \ - && apt-get install -y --no-install-recommends software-properties-common \ - && add-apt-repository ppa:ubuntu-toolchain-r/test \ - && apt-get update \ - && apt-get upgrade -y \ - && apt-get dist-upgrade -y \ - && apt-get install -y --no-install-recommends \ - libmariadb2 \ - mariadb-client \ - libssl1.0.0 \ - && rm -rf /var/lib/apt/lists/* \ - && mkdir -p /root/.byond/bin - -COPY --from=rust_g /rust_g/target/release/librust_g.so /root/.byond/bin/rust_g COPY --from=build /deploy ./ +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/_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/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/RandomZLevels/away_mission/jungleresort.dmm b/_maps/RandomZLevels/away_mission/jungleresort.dmm index 4afe638edb..9ff92b619a 100644 --- a/_maps/RandomZLevels/away_mission/jungleresort.dmm +++ b/_maps/RandomZLevels/away_mission/jungleresort.dmm @@ -413,10 +413,6 @@ /obj/item/toy/figure/chef, /turf/open/floor/wood, /area/awaymission/jungleresort) -"gC" = ( -/obj/item/clothing/head/rice_hat/cursed, -/turf/open/floor/plating/dirt/jungle, -/area/awaymission/jungleresort) "gK" = ( /obj/structure/table/wood, /obj/item/gun/ballistic/automatic/c20r/toy/unrestricted, @@ -13828,7 +13824,7 @@ io du YF io -gC +io io io ia @@ -26666,4 +26662,3 @@ bG bG bG "} - diff --git a/_maps/map_files/BoxStation/BoxStation.dmm b/_maps/map_files/BoxStation/BoxStation.dmm index f7245ef535..eb194cf5cf 100644 --- a/_maps/map_files/BoxStation/BoxStation.dmm +++ b/_maps/map_files/BoxStation/BoxStation.dmm @@ -30845,6 +30845,7 @@ /area/science) "bvF" = ( /obj/machinery/requests_console{ + announcementConsole = 1; department = "Cargo Bay"; departmentType = 2; pixel_x = -30 diff --git a/_maps/map_files/CogStation/CogStation.dmm b/_maps/map_files/CogStation/CogStation.dmm index e41f6c7d89..84968dc961 100644 --- a/_maps/map_files/CogStation/CogStation.dmm +++ b/_maps/map_files/CogStation/CogStation.dmm @@ -42885,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" = ( diff --git a/_maps/map_files/Deltastation/DeltaStation2.dmm b/_maps/map_files/Deltastation/DeltaStation2.dmm index 4e2b009c79..94699a7a00 100644 --- a/_maps/map_files/Deltastation/DeltaStation2.dmm +++ b/_maps/map_files/Deltastation/DeltaStation2.dmm @@ -106349,10 +106349,10 @@ /obj/effect/turf_decal/tile/neutral{ dir = 8 }, +/obj/structure/closet/crate/freezer/blood, /turf/open/floor/plasteel/dark, /area/medical/surgery) "dyK" = ( -/obj/structure/closet/crate/freezer/blood, /obj/machinery/vending/wallmed{ name = "Emergency NanoMed"; pixel_x = 26; @@ -106368,6 +106368,7 @@ /obj/effect/turf_decal/tile/neutral{ dir = 8 }, +/obj/machinery/limbgrower, /turf/open/floor/plasteel/dark, /area/medical/surgery) "dyL" = ( diff --git a/_maps/map_files/PubbyStation/PubbyStation.dmm b/_maps/map_files/PubbyStation/PubbyStation.dmm index 12c507c2bf..8e23df2e5f 100644 --- a/_maps/map_files/PubbyStation/PubbyStation.dmm +++ b/_maps/map_files/PubbyStation/PubbyStation.dmm @@ -37478,11 +37478,10 @@ /turf/open/floor/plasteel/white, /area/medical/medbay/central) "bHb" = ( -/obj/structure/closet/crate/freezer/surplus_limbs, -/obj/item/reagent_containers/glass/beaker/synthflesh, /obj/machinery/newscaster/security_unit{ pixel_y = 32 }, +/obj/machinery/limbgrower, /turf/open/floor/plasteel/freezer, /area/medical/surgery) "bHc" = ( @@ -38110,6 +38109,8 @@ /obj/effect/turf_decal/tile/blue{ dir = 4 }, +/obj/structure/closet/crate/freezer/surplus_limbs, +/obj/item/reagent_containers/glass/beaker/synthflesh, /turf/open/floor/plasteel/white, /area/medical/surgery) "bIq" = ( 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/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/signals.dm b/code/__DEFINES/dcs/signals.dm index 42199174e7..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" ////////////////////////////////////////////////////////////////// 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/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/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/reactions.dm b/code/__DEFINES/reactions.dm index 8f01f453b6..b5d322a091 100644 --- a/code/__DEFINES/reactions.dm +++ b/code/__DEFINES/reactions.dm @@ -21,10 +21,10 @@ #define STIMULUM_FIRST_DROP 0.065 #define STIMULUM_SECOND_RISE 0.0009 #define STIMULUM_ABSOLUTE_DROP 0.00000335 -#define REACTION_OPPRESSION_THRESHOLD 5 +#define REACTION_OPPRESSION_THRESHOLD 10 #define NOBLIUM_FORMATION_ENERGY 2e9 //1 Mole of Noblium takes the planck energy to condense. //Research point amounts -#define NOBLIUM_RESEARCH_AMOUNT 1000 +#define NOBLIUM_RESEARCH_AMOUNT 25 #define BZ_RESEARCH_SCALE 4 #define BZ_RESEARCH_MAX_AMOUNT 400 #define MIASMA_RESEARCH_AMOUNT 6 diff --git a/code/__DEFINES/robots.dm b/code/__DEFINES/robots.dm index dda9cfd430..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 @@ -69,8 +71,15 @@ //Checks to determine borg availability depending on the server's config. These are defines in the interest of reducing copypasta #define BORG_SEC_AVAILABLE (!CONFIG_GET(flag/disable_secborg) && GLOB.security_level >= CONFIG_GET(number/minimum_secborg_alert)) -//silicon_privileges flags +//silicon_priviledges flags #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/subsystems.dm b/code/__DEFINES/subsystems.dm index e84f6eb50b..9c67a6b36c 100644 --- a/code/__DEFINES/subsystems.dm +++ b/code/__DEFINES/subsystems.dm @@ -108,7 +108,7 @@ #define INIT_ORDER_SOUNDS 83 #define INIT_ORDER_INSTRUMENTS 82 #define INIT_ORDER_VIS 80 -// #define INIT_ORDER_ACHIEVEMENTS 77 +#define INIT_ORDER_ACHIEVEMENTS 77 #define INIT_ORDER_RESEARCH 75 #define INIT_ORDER_EVENTS 70 #define INIT_ORDER_JOBS 65 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/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/_lists.dm b/code/__HELPERS/_lists.dm index a554397c41..31f34c5d4c 100644 --- a/code/__HELPERS/_lists.dm +++ b/code/__HELPERS/_lists.dm @@ -614,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/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/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/unsorted.dm b/code/__HELPERS/unsorted.dm index 71bbfe64fe..ee4b09d42c 100644 --- a/code/__HELPERS/unsorted.dm +++ b/code/__HELPERS/unsorted.dm @@ -1247,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) @@ -1579,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/_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 41068048a1..ac86912c84 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", @@ -152,14 +153,20 @@ GLOBAL_LIST_INIT(ai_core_display_screens, list( "Yes-Man" )) -/proc/resolve_ai_icon(input) +/proc/resolve_ai_icon(input, radial_preview = FALSE) if(!input || !(input in GLOB.ai_core_display_screens)) return "ai" - else - if(input == "Random") - input = pick(GLOB.ai_core_display_screens - "Random") + if(radial_preview) return "ai-[lowertext(input)]" + 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)) //Backpacks 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/plane_master.dm b/code/_onclick/hud/plane_master.dm index f5b8991e20..4c736c9a2b 100644 --- a/code/_onclick/hud/plane_master.dm +++ b/code/_onclick/hud/plane_master.dm @@ -38,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" 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/item_attack.dm b/code/_onclick/item_attack.dm index bedb0f6dec..77650be94b 100644 --- a/code/_onclick/item_attack.dm +++ b/code/_onclick/item_attack.dm @@ -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/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 f6a3388c01..08989469f5 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 @@ -605,3 +612,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 4d985c7234..63da60d7b5 100644 --- a/code/controllers/configuration/entries/general.dm +++ b/code/controllers/configuration/entries/general.dm @@ -275,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 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/blackbox.dm b/code/controllers/subsystem/blackbox.dm index bacccefc61..5d236045fd 100644 --- a/code/controllers/subsystem/blackbox.dm +++ b/code/controllers/subsystem/blackbox.dm @@ -5,10 +5,10 @@ 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, @@ -28,12 +28,12 @@ SUBSYSTEM_DEF(blackbox) //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() @@ -43,7 +43,17 @@ SUBSYSTEM_DEF(blackbox) return 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) @@ -87,24 +97,23 @@ SUBSYSTEM_DEF(blackbox) if (!SSdbcore.Connect()) return - // var/list/special_columns = list( - // "datetime" = "NOW()" - // ) + var/list/special_columns = list( + "datetime" = "NOW()" + ) var/list/sqlrowlist = list() for (var/datum/feedback_variable/FV in feedback) sqlrowlist += list(list( - "datetime" = "Now()", //legacy "round_id" = GLOB.round_id, - "key_name" = sanitizeSQL(FV.key), + "key_name" = FV.key, "key_type" = FV.key_type, "version" = versions[FV.key] || 1, - "json" = sanitizeSQL(json_encode(FV.json)) + "json" = json_encode(FV.json) )) if (!length(sqlrowlist)) return - SSdbcore.MassInsert(format_table_name("feedback"), sqlrowlist, ignore_errors = TRUE, delayed = TRUE)//, special_columns = special_columns) + SSdbcore.MassInsert(format_table_name("feedback"), sqlrowlist, ignore_errors = TRUE, delayed = TRUE, special_columns = special_columns) /datum/controller/subsystem/blackbox/proc/Seal() if(sealed) @@ -162,13 +171,13 @@ 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" @@ -176,7 +185,7 @@ feedback data can be recorded in 5 formats: further calls to the same key will: add or subtract from the saved value of the data key if it already exists append the key and it's value if it doesn't exist - 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}} @@ -188,19 +197,19 @@ feedback data can be recorded in 5 formats: 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 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"}}} @@ -275,7 +284,7 @@ Versioning /datum/feedback_variable/New(new_key, new_key_type) key = new_key key_type = new_key_type -/* + /datum/controller/subsystem/blackbox/proc/LogAhelp(ticket, action, message, recipient, sender) if(!SSdbcore.Connect()) return @@ -286,7 +295,7 @@ Versioning "}, 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 @@ -302,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/dbcore.dm b/code/controllers/subsystem/dbcore.dm index c779c9f26d..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,32 +174,34 @@ 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/DBQuery)) + if (!istype(querys, /datum/db_query)) CRASH("Invalid query passed to QuerySelect: [querys]") querys = list(querys) for (var/thing in querys) - var/datum/DBQuery/query = thing + var/datum/db_query/query = thing if (warn) - INVOKE_ASYNC(query, /datum/DBQuery.proc/warn_execute) + INVOKE_ASYNC(query, /datum/db_query.proc/warn_execute) else - INVOKE_ASYNC(query, /datum/DBQuery.proc/Execute) + INVOKE_ASYNC(query, /datum/db_query.proc/Execute) for (var/thing in querys) - var/datum/DBQuery/query = thing + 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. Rows missing columns present in other rows will resolve to SQL NULL @@ -203,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]") @@ -341,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/job.dm b/code/controllers/subsystem/job.dm index 0ba6076be2..c090d7367c 100644 --- a/code/controllers/subsystem/job.dm +++ b/code/controllers/subsystem/job.dm @@ -496,9 +496,15 @@ SUBSYSTEM_DEF(job) 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) - 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) + 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 @@ -508,9 +514,15 @@ SUBSYSTEM_DEF(job) 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) - 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) + 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 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/persistence/_persistence.dm b/code/controllers/subsystem/persistence/_persistence.dm index 9b2c019db4..d494561d0f 100644 --- a/code/controllers/subsystem/persistence/_persistence.dm +++ b/code/controllers/subsystem/persistence/_persistence.dm @@ -88,7 +88,6 @@ SUBSYSTEM_DEF(persistence) SavePhotoPersistence() //THIS IS PERSISTENCE, NOT THE LOGGING PORTION. SavePaintings() SaveScars() - SaveTCGCards() /** * Loads persistent data relevant to the current map: Objects, etc. 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/processing.dm b/code/controllers/subsystem/processing/processing.dm index 5cefd3a148..f5a423b353 100644 --- a/code/controllers/subsystem/processing/processing.dm +++ b/code/controllers/subsystem/processing/processing.dm @@ -47,5 +47,5 @@ SUBSYSTEM_DEF(processing) * 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 + // SHOULD_NOT_SLEEP(TRUE) return PROCESS_KILL 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/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/stickyban.dm b/code/controllers/subsystem/stickyban.dm index 0c71777bc0..c61ea9943e 100644 --- a/code/controllers/subsystem/stickyban.dm +++ b/code/controllers/subsystem/stickyban.dm @@ -60,15 +60,10 @@ SUBSYSTEM_DEF(stickyban) /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") - - var/datum/DBQuery/query_stickybans = SSdbcore.NewQuery("SELECT ckey, reason, banning_admin, datetime FROM [format_table_name("stickyban")] ORDER BY ckey") - var/datum/DBQuery/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/DBQuery/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/DBQuery/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") + 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)) @@ -161,25 +156,15 @@ SUBSYSTEM_DEF(stickyban) if (!ban["message"]) ban["message"] = "Evasion" - // TODO: USE NEW DB IMPLEMENTATION - var/datum/DBQuery/query_create_stickyban = SSdbcore.NewQuery( - "INSERT IGNORE INTO [format_table_name("stickyban")] (ckey, reason, banning_admin) VALUES ([ckey], [ban["message"]], ban["admin"]))" + 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()) + if (!query_create_stickyban.warn_execute()) qdel(query_create_stickyban) return qdel(query_create_stickyban) - // 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() 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/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/datums/achievements/_achievement_data.dm b/code/datums/achievements/_achievement_data.dm new file mode 100644 index 0000000000..80f544a965 --- /dev/null +++ b/code/datums/achievements/_achievement_data.dm @@ -0,0 +1,149 @@ +///Datum that handles +/datum/achievement_data + ///Ckey of this achievement data's owner + var/owner_ckey + ///Up to date list of all achievements and their info. + var/data = list() + ///Original status of achievement. + var/original_cached_data = list() + ///Have we done our set-up yet? + var/initialized = FALSE + +/datum/achievement_data/New(ckey) + owner_ckey = ckey + if(SSachievements.initialized && !initialized) + InitializeData() + +/datum/achievement_data/proc/InitializeData() + initialized = TRUE + load_all_achievements() //So we know which achievements we have unlocked so far. + +///Gets list of changed rows in MassInsert format +/datum/achievement_data/proc/get_changed_data() + . = list() + for(var/T in data) + var/datum/award/A = SSachievements.awards[T] + if(data[T] != original_cached_data[T])//If our data from before is not the same as now, save it to db. + var/deets = A.get_changed_rows(owner_ckey,data[T]) + if(deets) + . += list(deets) + +/datum/achievement_data/proc/load_all_achievements() + set waitfor = FALSE + + var/list/kv = list() + var/datum/db_query/Query = SSdbcore.NewQuery( + "SELECT achievement_key,value FROM [format_table_name("achievements")] WHERE ckey = :ckey", + list("ckey" = owner_ckey) + ) + if(!Query.Execute()) + qdel(Query) + return + while(Query.NextRow()) + var/key = Query.item[1] + var/value = text2num(Query.item[2]) + kv[key] = value + qdel(Query) + + for(var/T in subtypesof(/datum/award)) + var/datum/award/A = SSachievements.awards[T] + if(!A || !A.name) //Skip abstract achievements types + continue + if(!data[T]) + data[T] = A.parse_value(kv[A.database_id]) + original_cached_data[T] = data[T] + +///Updates local cache with db data for the given achievement type if it wasn't loaded yet. +/datum/achievement_data/proc/get_data(achievement_type) + var/datum/award/A = SSachievements.awards[achievement_type] + if(!A.name) + return FALSE + if(!data[achievement_type]) + data[achievement_type] = A.load(owner_ckey) + original_cached_data[achievement_type] = data[achievement_type] + +///Unlocks an achievement of a specific type. achievement type is a typepath to the award, user is the mob getting the award, and value is an optional value to be used for defining a score to add to the leaderboard +/datum/achievement_data/proc/unlock(achievement_type, mob/user, value = 1) + set waitfor = FALSE + + if(!SSachievements.achievements_enabled) + return + var/datum/award/A = SSachievements.awards[achievement_type] + get_data(achievement_type) //Get the current status first if necessary + if(istype(A, /datum/award/achievement)) + if(data[achievement_type]) //You already unlocked it so don't bother running the unlock proc + return + data[achievement_type] = TRUE + A.on_unlock(user) //Only on default achievement, as scores keep going up. + else if(istype(A, /datum/award/score)) + data[achievement_type] += value + +///Getter for the status/score of an achievement +/datum/achievement_data/proc/get_achievement_status(achievement_type) + return data[achievement_type] + +///Resets an achievement to default values. +/datum/achievement_data/proc/reset(achievement_type) + if(!SSachievements.achievements_enabled) + return + var/datum/award/A = SSachievements.awards[achievement_type] + get_data(achievement_type) + if(istype(A, /datum/award/achievement)) + data[achievement_type] = FALSE + else if(istype(A, /datum/award/score)) + data[achievement_type] = 0 + +/datum/achievement_data/ui_assets(mob/user) + return list( + get_asset_datum(/datum/asset/spritesheet/simple/achievements), + ) + +/datum/achievement_data/ui_state(mob/user) + return GLOB.always_state + +/datum/achievement_data/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "Achievements") + ui.open() + +/datum/achievement_data/ui_data(mob/user) + var/ret_data = list() // screw standards (qustinnus you must rename src.data ok) + ret_data["categories"] = list("Bosses", "Misc", "Mafia", "Scores") + ret_data["achievements"] = list() + ret_data["user_key"] = user.ckey + + var/datum/asset/spritesheet/simple/assets = get_asset_datum(/datum/asset/spritesheet/simple/achievements) + //This should be split into static data later + for(var/achievement_type in SSachievements.awards) + if(!SSachievements.awards[achievement_type].name) //No name? we a subtype. + continue + if(isnull(data[achievement_type])) //We're still loading + continue + var/list/this = list( + "name" = SSachievements.awards[achievement_type].name, + "desc" = SSachievements.awards[achievement_type].desc, + "category" = SSachievements.awards[achievement_type].category, + "icon_class" = assets.icon_class_name(SSachievements.awards[achievement_type].icon), + "value" = data[achievement_type], + "score" = ispath(achievement_type,/datum/award/score) + ) + ret_data["achievements"] += list(this) + + return ret_data + +/datum/achievement_data/ui_static_data(mob/user) + . = ..() + .["highscore"] = list() + for(var/score in SSachievements.scores) + var/datum/award/score/S = SSachievements.scores[score] + if(!S.name || !S.track_high_scores || !S.high_scores.len) + continue + .["highscore"] += list(list("name" = S.name,"scores" = S.high_scores)) + +/client/verb/checkachievements() + set category = "OOC" + set name = "Check achievements" + set desc = "See all of your achievements!" + + player_details.achievements.ui_interact(usr) diff --git a/code/datums/achievements/_awards.dm b/code/datums/achievements/_awards.dm new file mode 100644 index 0000000000..77485de8ba --- /dev/null +++ b/code/datums/achievements/_awards.dm @@ -0,0 +1,117 @@ +/datum/award + ///Name of the achievement, If null it won't show up in the achievement browser. (Handy for inheritance trees) + var/name + var/desc = "You did it." + ///Found in UI_Icons/Achievements + var/icon = "default" + var/category = "Normal" + + ///What ID do we use in db, limited to 32 characters + var/database_id + //Bump this up if you're changing outdated table identifier and/or achievement type + var/achievement_version = 2 + + //Value returned on db connection failure, in case we want to differ 0 and nonexistent later on + var/default_value = FALSE + +///This proc loads the achievement data from the hub. +/datum/award/proc/load(key) + if(!SSdbcore.Connect()) + return default_value + if(!key || !database_id || !name) + return default_value + var/raw_value = get_raw_value(key) + return parse_value(raw_value) + +///This saves the changed data to the hub. +/datum/award/proc/get_changed_rows(key, value) + if(!database_id || !key || !name) + return + return list( + "ckey" = key, + "achievement_key" = database_id, + "value" = value, + ) + +/datum/award/proc/get_metadata_row() + return list( + "achievement_key" = database_id, + "achievement_version" = achievement_version, + "achievement_type" = "award", + "achievement_name" = name, + "achievement_description" = desc, + ) + +///Get raw numerical achievement value from the database +/datum/award/proc/get_raw_value(key) + var/datum/db_query/Q = SSdbcore.NewQuery( + "SELECT value FROM [format_table_name("achievements")] WHERE ckey = :ckey AND achievement_key = :achievement_key", + list("ckey" = key, "achievement_key" = database_id) + ) + if(!Q.Execute(async = TRUE)) + qdel(Q) + return 0 + var/result = 0 + if(Q.NextRow()) + result = text2num(Q.item[1]) + qdel(Q) + return result + +//Should return sanitized value for achievement cache +/datum/award/proc/parse_value(raw_value) + return default_value + +///Can be overriden for achievement specific events +/datum/award/proc/on_unlock(mob/user) + return + +///Achievements are one-off awards for usually doing cool things. +/datum/award/achievement + desc = "Achievement for epic people" + +/datum/award/achievement/get_metadata_row() + . = ..() + .["achievement_type"] = "achievement" + +/datum/award/achievement/parse_value(raw_value) + return raw_value > 0 + +/datum/award/achievement/on_unlock(mob/user) + . = ..() + to_chat(user, "Achievement unlocked: [name]!") + +///Scores are for leaderboarded things, such as killcount of a specific boss +/datum/award/score + desc = "you did it sooo many times." + category = "Scores" + default_value = 0 + + var/track_high_scores = TRUE + var/list/high_scores = list() + +/datum/award/score/New() + . = ..() + if(track_high_scores) + LoadHighScores() + +/datum/award/score/get_metadata_row() + . = ..() + .["achievement_type"] = "score" + +/datum/award/score/proc/LoadHighScores() + var/datum/db_query/Q = SSdbcore.NewQuery( + "SELECT ckey,value FROM [format_table_name("achievements")] WHERE achievement_key = :achievement_key ORDER BY value DESC LIMIT 50", + list("achievement_key" = database_id) + ) + if(!Q.Execute(async = TRUE)) + qdel(Q) + return + else + while(Q.NextRow()) + var/key = Q.item[1] + var/score = text2num(Q.item[2]) + high_scores[key] = score + qdel(Q) + +/datum/award/score/parse_value(raw_value) + return isnum(raw_value) ? raw_value : 0 diff --git a/code/datums/achievements/boss_achievements.dm b/code/datums/achievements/boss_achievements.dm new file mode 100644 index 0000000000..104a369405 --- /dev/null +++ b/code/datums/achievements/boss_achievements.dm @@ -0,0 +1,130 @@ +/datum/award/achievement/boss + category = "Bosses" + icon = "baseboss" + +/datum/award/achievement/boss/tendril_exterminator + name = "Tendril Exterminator" + desc = "Watch your step" + database_id = BOSS_MEDAL_TENDRIL + icon = "tendril" + +/datum/award/achievement/boss/boss_killer + name = "Boss Killer" + desc = "You've come a long ways from asking how to switch hands." + database_id = "Boss Killer" + // icon = "firstboss" + +/datum/award/achievement/boss/blood_miner_kill + name = "Blood-Drunk Miner Killer" + desc = "I guess he couldn't handle his drink that well." + database_id = BOSS_MEDAL_MINER + icon = "miner" + +/datum/award/achievement/boss/demonic_miner_kill + name = "Demonic-Frost Miner Killer" + desc = "Definitely harder than the Blood-Drunk Miner." + database_id = BOSS_MEDAL_FROSTMINER + +/datum/award/achievement/boss/bubblegum_kill + name = "Bubblegum Killer" + desc = "I guess he wasn't made of candy after all" + database_id = BOSS_MEDAL_BUBBLEGUM + icon = "bbgum" + +/datum/award/achievement/boss/colossus_kill + name = "Colossus Killer" + desc = "The bigger they are... the better the loot" + database_id = BOSS_MEDAL_COLOSSUS + icon = "colossus" + +/datum/award/achievement/boss/drake_kill + name = "Drake Killer" + desc = "Now I can wear Rune Platebodies!" + database_id = BOSS_MEDAL_DRAKE + icon = "drake" + +/datum/award/achievement/boss/hierophant_kill + name = "Hierophant Killer" + desc = "Hierophant, but not triumphant." + database_id = BOSS_MEDAL_HIEROPHANT + icon = "hierophant" + +/datum/award/achievement/boss/legion_kill + name = "Legion Killer" + desc = "We were many..now we are none." + database_id = BOSS_MEDAL_LEGION + icon = "legion" + +/datum/award/achievement/boss/swarmer_beacon_kill + name = "Swarm Beacon Killer" + desc = "GET THEM OFF OF ME!" + database_id = BOSS_MEDAL_SWARMERS + icon = "swarmer" + +/datum/award/achievement/boss/wendigo_kill + name = "Wendigo Killer" + desc = "You've now ruined years of mythical storytelling." + database_id = BOSS_MEDAL_WENDIGO + +/datum/award/achievement/boss/blood_miner_crusher + name = "Blood-Drunk Miner Crusher" + desc = "I guess he couldn't handle his drink that well." + database_id = BOSS_MEDAL_MINER_CRUSHER + icon = "miner" + +/datum/award/achievement/boss/demonic_miner_crusher + name = "Demonic-Frost Miner Crusher" + desc = "Definitely harder than the Blood-Drunk Miner." + database_id = BOSS_MEDAL_FROSTMINER_CRUSHER + +/datum/award/achievement/boss/bubblegum_crusher + name = "Bubblegum Crusher" + desc = "I guess he wasn't made of candy after all" + database_id = BOSS_MEDAL_BUBBLEGUM_CRUSHER + icon = "bbgum" + +/datum/award/achievement/boss/colossus_crusher + name = "Colossus Crusher" + desc = "The bigger they are... the better the loot" + database_id = BOSS_MEDAL_COLOSSUS_CRUSHER + icon = "colossus" + +/datum/award/achievement/boss/drake_crusher + name = "Drake Crusher" + desc = "Now I can wear Rune Platebodies!" + database_id = BOSS_MEDAL_DRAKE_CRUSHER + icon = "drake" + +/datum/award/achievement/boss/hierophant_crusher + name = "Hierophant Crusher" + desc = "Hierophant, but not triumphant." + database_id = BOSS_MEDAL_HIEROPHANT_CRUSHER + icon = "hierophant" + +/datum/award/achievement/boss/legion_crusher + name = "Legion Crusher" + desc = "We were many... now we are none." + database_id = BOSS_MEDAL_LEGION_CRUSHER + +/datum/award/achievement/boss/swarmer_beacon_crusher + name = "Swarm Beacon Crusher" + desc = "GET THEM OFF OF ME!" + database_id = BOSS_MEDAL_SWARMERS_CRUSHER + +/datum/award/achievement/boss/wendigo_crusher + name = "Wendigo Crusher" + desc = "You've now ruined years of mythical storytelling." + database_id = BOSS_MEDAL_WENDIGO_CRUSHER + +//should be removed soon +// /datum/award/achievement/boss/king_goat_kill +// name = "King Goat Killer" +// desc = "The king is dead, long live the king!" +// database_id = BOSS_MEDAL_KINGGOAT +// icon = "goatboss" + +// /datum/award/achievement/boss/king_goat_crusher +// name = "King Goat Crusher" +// desc = "The king is dead, long live the king!" +// database_id = BOSS_MEDAL_KINGGOAT_CRUSHER +// icon = "goatboss" diff --git a/code/datums/achievements/boss_scores.dm b/code/datums/achievements/boss_scores.dm new file mode 100644 index 0000000000..fdb9efa7c7 --- /dev/null +++ b/code/datums/achievements/boss_scores.dm @@ -0,0 +1,54 @@ +/datum/award/score/tendril_score + name = "Tendril Score" + desc = "Watch your step" + database_id = TENDRIL_CLEAR_SCORE + +/datum/award/score/boss_score + name = "Bosses Killed" + desc = "You've killed HOW many?" + database_id = BOSS_SCORE + +/datum/award/score/blood_miner_score + name = "Blood-Drunk Miners Killed" + desc = "You've killed HOW many?" + database_id = MINER_SCORE + +/datum/award/score/demonic_miner_score + name = "Demonic-Frost Miners Killed" + desc = "You've killed HOW many?" + database_id = FROST_MINER_SCORE + +/datum/award/score/bubblegum_score + name = "Bubblegums Killed" + desc = "You've killed HOW many?" + database_id = BUBBLEGUM_SCORE + +/datum/award/score/colussus_score + name = "Colossus Killed" + desc = "You've killed HOW many?" + database_id = COLOSSUS_SCORE + +/datum/award/score/drake_score + name = "Drakes Killed" + desc = "You've killed HOW many?" + database_id = DRAKE_SCORE + +/datum/award/score/hierophant_score + name = "Hierophants Killed" + desc = "You've killed HOW many?" + database_id = HIEROPHANT_SCORE + +/datum/award/score/legion_score + name = "Legions Killed" + desc = "You've killed HOW many?" + database_id = LEGION_SCORE + +/datum/award/score/swarmer_beacon_score + name = "Swarmer Beacons Killed" + desc = "You've killed HOW many?" + database_id = SWARMER_BEACON_SCORE + +/datum/award/score/wendigo_score + name = "Wendigos Killed" + desc = "You've killed HOW many?" + database_id = WENDIGO_SCORE diff --git a/code/datums/achievements/mafia_achievements.dm b/code/datums/achievements/mafia_achievements.dm new file mode 100644 index 0000000000..fbe3486397 --- /dev/null +++ b/code/datums/achievements/mafia_achievements.dm @@ -0,0 +1,115 @@ +/datum/award/achievement/mafia + category = "Mafia" + icon = "basemafia" + +///ALL THE ACHIEVEMENTS FOR WINNING A ROUND AS A ROLE/// + +/datum/award/achievement/mafia/assistant + name = "Assistant Victory" + desc = "If you got killed instead of someone more important, you just flexed the true strength of your \"\"\"\"role\"\"\"\"." + database_id = MAFIA_MEDAL_ASSISTANT + icon = "town" + +/datum/award/achievement/mafia/detective + name = "Detective Victory" + desc = "If you did this with a Medical Doctor in the game, i'm not really that impressed." + database_id = MAFIA_MEDAL_DETECTIVE + icon = "town" + +/datum/award/achievement/mafia/psychologist + name = "Psychologist Victory" + desc = "You learned how to not reveal someone random night one! Or... maybe you're just a lucky bastard." + database_id = MAFIA_MEDAL_PSYCHOLOGIST + icon = "town" + +/datum/award/achievement/mafia/chaplain + name = "Chaplain Victory" + desc = "Useless... until the one night the thoughtfeeder confidently claims themselves as detective. Mafia's true bullshit detector." + database_id = MAFIA_MEDAL_CHAPLAIN + icon = "town" + +/datum/award/achievement/mafia/md + name = "Medical Doctor Victory" + desc = "Congratulations on learning how to not talk!" + database_id = MAFIA_MEDAL_MD + icon = "town" + +/datum/award/achievement/mafia/officer + name = "Security Officer Victory" + desc = "Don't worry, you can win this if you're dead! You... did use your ability to become dead, right?" + database_id = MAFIA_MEDAL_OFFICER + icon = "town" + +/datum/award/achievement/mafia/lawyer + name = "Lawyer Victory" + desc = "Oh don't mind me, i'm just the worst rol- Oops, I just instantly ended the game." + database_id = MAFIA_MEDAL_LAWYER + icon = "town" + +/datum/award/achievement/mafia/hop + name = "Head of Personnel Victory" + desc = "King of Assistants, waster of a single mafia's night, thrower of games." + database_id = MAFIA_MEDAL_HOP + icon = "town" + +/datum/award/achievement/mafia/warden + name = "Warden Victory" + desc = "Make changelings think you're detective, go on lockdown, actual detective investigates you and dies. Cha cha real smooth!" + database_id = MAFIA_MEDAL_WARDEN + icon = "town" + +/datum/award/achievement/mafia/hos + name = "Head of Security Victory" + desc = "Certified not shitcurity." + database_id = MAFIA_MEDAL_HOS + icon = "town" + +/datum/award/achievement/mafia/changeling + name = "Changeling Victory" + desc = "I think the changelings are metacomming." + database_id = MAFIA_MEDAL_CHANGELING + icon = "mafia" + +/datum/award/achievement/mafia/thoughtfeeder + name = "Thoughtfeeder Victory" + desc = "Clown's best friend. And Obsessed. And fugitive? Whose side are you on?!" + database_id = MAFIA_MEDAL_THOUGHTFEEDER + icon = "mafia" + +/datum/award/achievement/mafia/traitor + name = "Traitor Victory" + desc = "Guys, we still have two more changelings to ki-!! TRAITOR VICTORY !!" + database_id = MAFIA_MEDAL_TRAITOR + icon = "neutral" + +/datum/award/achievement/mafia/nightmare + name = "Nightmare Victory" + desc = "DID YOUR LIGHT FLICKER?!" + database_id = MAFIA_MEDAL_NIGHTMARE + icon = "neutral" + +/datum/award/achievement/mafia/fugitive + name = "Fugitive Victory" + desc = "I'm just the description on an achievement, but if you end up having to choose between town and changelings, go changelings." + database_id = MAFIA_MEDAL_FUGITIVE + icon = "neutral" + +/datum/award/achievement/mafia/obsessed + name = "Obsessed Victory" + desc = "You got your target lynched, so instead of being spiteful and annoying, you're just smug and annoying." + database_id = MAFIA_MEDAL_OBSESSED + icon = "neutral" + +/datum/award/achievement/mafia/clown + name = "Clown Victory" + desc = "Did you know this works on traitors, despite their immunity? If you hit the jackpot and manage to kill one, they'll salt into the next dimension. Clown tips!" + database_id = MAFIA_MEDAL_CLOWN + icon = "neutral" + +///ALL THE ACHIEVEMENTS FOR MISC MAFIA ODDITIES/// + +/datum/award/achievement/mafia/universally_hated + name = "Universally Hated" + desc = "Managed to get more than 12 votes when put up on trial, jesus christ." + database_id = MAFIA_MEDAL_HATED + icon = "hated" diff --git a/code/datums/achievements/misc_achievements.dm b/code/datums/achievements/misc_achievements.dm new file mode 100644 index 0000000000..0da38df8f3 --- /dev/null +++ b/code/datums/achievements/misc_achievements.dm @@ -0,0 +1,161 @@ +/datum/award/achievement/misc + category = "Misc" + icon = "basemisc" + +/datum/award/achievement/misc/meteor_examine + name = "Your Life Before Your Eyes" + desc = "Take a close look at hurtling space debris" + database_id = MEDAL_METEOR + icon = "meteors" + +/datum/award/achievement/misc/pulse + name = "Jackpot" + desc = "Win a pulse rifle from an arcade machine" + database_id = MEDAL_PULSE + icon = "jackpot" + +/datum/award/achievement/misc/time_waste + name = "Time waster" + desc = "Speak no evil, hear no evil, see just errors" + database_id = MEDAL_TIMEWASTE + icon = "timewaste" + +/datum/award/achievement/misc/feat_of_strength + name = "Feat of Strength" + desc = "If the rod is immovable, is it passing you or are you passing it?" + database_id = MEDAL_RODSUPLEX + icon = "featofstrength" + +/datum/award/achievement/misc/round_and_full + name = "Round and Full" + desc = "Well at least you aren't down the river, I hear they eat people there." + database_id = MEDAL_CLOWNCARKING + icon = "clownking" + +/datum/award/achievement/misc/the_best_driver + name = "The Best Driver" + desc = "100 honks later" + database_id = MEDAL_THANKSALOT + icon = "clownthanks" + +/datum/award/achievement/misc/helbitaljanken + name = "Helbitaljanken" + desc = "You janked hard" + database_id = MEDAL_HELBITALJANKEN + icon = "helbital" + +/datum/award/achievement/misc/getting_an_upgrade + name = "Getting an upgrade" + desc = "Make your first unique material item!" + database_id = MEDAL_MATERIALCRAFT + +/datum/award/achievement/misc/rocket_holdup + name = "Disk, Please!" + desc = "Is the man currently pointing a loaded rocket launcher at your head point blank really dumb enough to pull the trigger? Do you really want to find out?" + database_id = MEDAL_DISKPLEASE + +/datum/award/achievement/misc/gamer + name = "My Watchlist Status is Not Important" + desc = "You may be under the impression that violent video games are a harmless pastime, but the security and medical personnel swarming your location with batons and knockout gas look like they disagree." + database_id = MEDAL_GAMER + +/datum/award/achievement/misc/vendor_squish + name = "I Was a Teenage Anarchist" + desc = "You were doing a great job sticking it to the system until that vending machine decided to fight back." + database_id = MEDAL_VENDORSQUISH + +/datum/award/achievement/misc/swirlie + name = "A Bowl-d New World" + desc = "There's a lot of grisly ways to kick it on the Spinward Periphery, but drowning to death in a toilet probably wasn't what you had in mind. Probably." + database_id = MEDAL_SWIRLIE + +/datum/award/achievement/misc/selfouch + name = "How Do I Switch Hands???" + desc = "If you saw someone casually club themselves upside the head with a toolbox anywhere in the galaxy but here, you'd probably be pretty concerned for them." + database_id = MEDAL_SELFOUCH + +/datum/award/achievement/misc/sandman + name = "Mister Sandman" + desc = "Mechanically speaking, there's no real benefit to being unconscious during surgery. Weird how insistent this doctor is about using the N2O anyway though, huh?" + database_id = MEDAL_SANDMAN + +/datum/award/achievement/misc/cleanboss + name = "One Lean, Mean, Cleaning Machine" + desc = "How does it feel to know that your workplace values a mop bucket on wheels more than you?" // i can do better than this give me time + database_id = MEDAL_CLEANBOSS + +/datum/award/achievement/misc/rule8 + name = "Rule 8" + desc = "Call an admin this is ILLEGAL!!" + database_id = MEDAL_RULE8 + icon = "rule8" + +/datum/award/achievement/misc/speed_round + name = "Long shift" + desc = "Well, that didn't take long." + database_id = MEDAL_LONGSHIFT + icon = "longshift" + +/datum/award/achievement/misc/snail + name = "KKKiiilll mmmeee" + desc = "You were a little too ambitious, but hey, I guess you're still alive?" + database_id = MEDAL_SNAIL + icon = "snail" + +/datum/award/achievement/misc/lookoutsir + name = "Look Out, Sir!" + desc = "Either awarded for making the ultimate sacrifice for your comrades, or a really dumb attempt at grenade jumping." + database_id = MEDAL_LOOKOUTSIR + +/datum/award/achievement/misc/gottem + name = "HA, GOTTEM" + desc = "Made you look!" + database_id = MEDAL_GOTTEM + +/datum/award/achievement/misc/ascension + name = "Ascension" + desc = "Caedite eos. Novit enim Dominus qui sunt eius." + database_id = MEDAL_ASCENSION + icon = "ascension" + +/datum/award/achievement/misc/frenching + name = "Frenching" + desc = "Just a taste, for science!" + database_id = MEDAL_FRENCHING + icon = "frenching" + +/datum/award/achievement/misc/ash_ascension + name = "Nightwatcher's Eyes" + desc = "You've risen above the flames, became one with the ashes. You've been reborn as one with the Nightwatcher." + database_id = MEDAL_ASH_ASCENSION + icon = "ashascend" + +/datum/award/achievement/misc/flesh_ascension + name = "Vortex of Arms" + desc = "You've became something more, something greater. A piece of the emperor resides within you, and you within him." + database_id = MEDAL_FLESH_ASCENSION + icon = "fleshascend" + +/datum/award/achievement/misc/rust_ascension + name = "Hills of Rust" + desc = "You've summoned a piece of the Hill of rust, and so the Hills welcome you." + database_id = MEDAL_RUST_ASCENSION + icon = "rustascend" + +/datum/award/achievement/misc/void_ascension + name = "All that perish" + desc = "Place of a different being, different time. Everything ends there... but maybe it is just the beginning?" + database_id = MEDAL_VOID_ASCENSION + icon = "voidascend" + +/datum/award/achievement/misc/toolbox_soul + name = "SOUL'd Out" + desc = "My eternal soul was destroyed to make a toolbox look funny and all I got was this achievement..." + database_id = MEDAL_TOOLBOX_SOUL + icon = "toolbox_soul" + +/datum/award/achievement/misc/chemistry_tut + name = "Perfect chemistry blossom" + desc = "Passed the chemistry tutorial with perfect purity!" + database_id = MEDAL_CHEM_TUT + icon = "chem_tut" diff --git a/code/datums/achievements/misc_scores.dm b/code/datums/achievements/misc_scores.dm new file mode 100644 index 0000000000..7ffc50c015 --- /dev/null +++ b/code/datums/achievements/misc_scores.dm @@ -0,0 +1,11 @@ +///How many times did we survive being a cripple? +/datum/award/score/hardcore_random + name = "Hardcore random points" + desc = "Well, I might be a blind, deaf, crippled guy, but hey, at least I'm alive." + database_id = HARDCORE_RANDOM_SCORE + +///How many maintenance pills did you eat? +/datum/award/score/maintenance_pill + name = "Maintenance Pills Consumed" + desc = "Wait why?" + database_id = MAINTENANCE_PILL_SCORE diff --git a/code/datums/achievements/skill_achievements.dm b/code/datums/achievements/skill_achievements.dm new file mode 100644 index 0000000000..7da936c61f --- /dev/null +++ b/code/datums/achievements/skill_achievements.dm @@ -0,0 +1,10 @@ +/datum/award/achievement/skill + category = "Skills" + icon = "baseskill" + +/datum/award/achievement/skill/legendary_miner + name = "Legendary miner" + desc = "No mere rock can stop me!" + database_id = MEDAL_LEGENDARY_MINER + icon = "mining" + diff --git a/code/datums/callback.dm b/code/datums/callback.dm index 62e10922f3..b5baea28f1 100644 --- a/code/datums/callback.dm +++ b/code/datums/callback.dm @@ -1,53 +1,53 @@ /** - *# Callback Datums - *A datum that holds a proc to be called on another object, used to track proccalls to other objects - * - * ## USAGE - * - * ``` - * var/datum/callback/C = new(object|null, /proc/type/path|"procstring", arg1, arg2, ... argn) - * var/timerid = addtimer(C, time, timertype) - * you can also use the compiler define shorthand - * var/timerid = addtimer(CALLBACK(object|null, /proc/type/path|procstring, arg1, arg2, ... argn), time, timertype) - * ``` - * - * Note: proc strings can only be given for datum proc calls, global procs must be proc paths - * - * Also proc strings are strongly advised against because they don't compile error if the proc stops existing - * - * In some cases you can provide a shortform of the procname, see the proc typepath shortcuts documentation below - * - * ## INVOKING THE CALLBACK - *`var/result = C.Invoke(args, to, add)` additional args are added after the ones given when the callback was created - * - * `var/result = C.InvokeAsync(args, to, add)` Asyncronous - returns . on the first sleep then continues on in the background - * after the sleep/block ends, otherwise operates normally. - * - * ## PROC TYPEPATH SHORTCUTS - * (these operate on paths, not types, so to these shortcuts, datum is NOT a parent of atom, etc...) - * - * ### global proc while in another global proc: - * .procname - * - * `CALLBACK(GLOBAL_PROC, .some_proc_here)` - * - * ### proc defined on current(src) object (when in a /proc/ and not an override) OR overridden at src or any of it's parents: - * .procname - * - * `CALLBACK(src, .some_proc_here)` - * - * ### when the above doesn't apply: - *.proc/procname - * - * `CALLBACK(src, .proc/some_proc_here)` - * - * - * proc defined on a parent of a some type - * - * `/some/type/.proc/some_proc_here` - * - * Otherwise you must always provide the full typepath of the proc (/type/of/thing/proc/procname) - */ + *# Callback Datums + *A datum that holds a proc to be called on another object, used to track proccalls to other objects + * + * ## USAGE + * + * ``` + * var/datum/callback/C = new(object|null, /proc/type/path|"procstring", arg1, arg2, ... argn) + * var/timerid = addtimer(C, time, timertype) + * you can also use the compiler define shorthand + * var/timerid = addtimer(CALLBACK(object|null, /proc/type/path|procstring, arg1, arg2, ... argn), time, timertype) + * ``` + * + * Note: proc strings can only be given for datum proc calls, global procs must be proc paths + * + * Also proc strings are strongly advised against because they don't compile error if the proc stops existing + * + * In some cases you can provide a shortform of the procname, see the proc typepath shortcuts documentation below + * + * ## INVOKING THE CALLBACK + *`var/result = C.Invoke(args, to, add)` additional args are added after the ones given when the callback was created + * + * `var/result = C.InvokeAsync(args, to, add)` Asyncronous - returns . on the first sleep then continues on in the background + * after the sleep/block ends, otherwise operates normally. + * + * ## PROC TYPEPATH SHORTCUTS + * (these operate on paths, not types, so to these shortcuts, datum is NOT a parent of atom, etc...) + * + * ### global proc while in another global proc: + * .procname + * + * `CALLBACK(GLOBAL_PROC, .some_proc_here)` + * + * ### proc defined on current(src) object (when in a /proc/ and not an override) OR overridden at src or any of it's parents: + * .procname + * + * `CALLBACK(src, .some_proc_here)` + * + * ### when the above doesn't apply: + *.proc/procname + * + * `CALLBACK(src, .proc/some_proc_here)` + * + * + * proc defined on a parent of a some type + * + * `/some/type/.proc/some_proc_here` + * + * Otherwise you must always provide the full typepath of the proc (/type/of/thing/proc/procname) + */ /datum/callback ///The object we will be calling the proc on @@ -60,13 +60,13 @@ var/datum/weakref/user /** - * Create a new callback datum - * - * Arguments - * * thingtocall the object to call the proc on - * * proctocall the proc to call on the target object - * * ... an optional list of extra arguments to pass to the proc - */ + * Create a new callback datum + * + * Arguments + * * thingtocall the object to call the proc on + * * proctocall the proc to call on the target object + * * ... an optional list of extra arguments to pass to the proc + */ /datum/callback/New(thingtocall, proctocall, ...) if (thingtocall) object = thingtocall @@ -76,13 +76,13 @@ if(usr) user = WEAKREF(usr) /** - * Immediately Invoke proctocall on thingtocall, with waitfor set to false - * - * Arguments: - * * thingtocall Object to call on - * * proctocall Proc to call on that object - * * ... optional list of arguments to pass as arguments to the proc being called - */ + * Immediately Invoke proctocall on thingtocall, with waitfor set to false + * + * Arguments: + * * thingtocall Object to call on + * * proctocall Proc to call on that object + * * ... optional list of arguments to pass as arguments to the proc being called + */ /world/proc/ImmediateInvokeAsync(thingtocall, proctocall, ...) set waitfor = FALSE @@ -97,13 +97,13 @@ call(thingtocall, proctocall)(arglist(calling_arguments)) /** - * Invoke this callback - * - * Calls the registered proc on the registered object, if the user ref - * can be resolved it also inclues that as an arg - * - * If the datum being called on is varedited, the call is wrapped via WrapAdminProcCall - */ + * Invoke this callback + * + * Calls the registered proc on the registered object, if the user ref + * can be resolved it also inclues that as an arg + * + * If the datum being called on is varedited, the call is wrapped via [WrapAdminProcCall][/proc/WrapAdminProcCall] + */ /datum/callback/proc/Invoke(...) if(!usr) var/datum/weakref/W = user @@ -130,13 +130,13 @@ return call(object, delegate)(arglist(calling_arguments)) /** - * Invoke this callback async (waitfor=false) - * - * Calls the registered proc on the registered object, if the user ref - * can be resolved it also inclues that as an arg - * - * If the datum being called on is varedited, the call is wrapped via WrapAdminProcCall - */ + * Invoke this callback async (waitfor=false) + * + * Calls the registered proc on the registered object, if the user ref + * can be resolved it also inclues that as an arg + * + * If the datum being called on is varedited, the call is wrapped via WrapAdminProcCall + */ /datum/callback/proc/InvokeAsync(...) set waitfor = FALSE @@ -166,7 +166,7 @@ /** Helper datum for the select callbacks proc - */ + */ /datum/callback_select var/list/finished var/pendingcount @@ -192,16 +192,16 @@ finished[index] = rtn /** - * Runs a list of callbacks asyncronously, returning only when all have finished - * - * Callbacks can be repeated, to call it multiple times - * - * Arguments: - * * list/callbacks the list of callbacks to be called - * * list/callback_args the list of lists of arguments to pass into each callback - * * savereturns Optionally save and return the list of returned values from each of the callbacks - * * resolution The number of byond ticks between each time you check if all callbacks are complete - */ + * Runs a list of callbacks asyncronously, returning only when all have finished + * + * Callbacks can be repeated, to call it multiple times + * + * Arguments: + * * list/callbacks the list of callbacks to be called + * * list/callback_args the list of lists of arguments to pass into each callback + * * savereturns Optionally save and return the list of returned values from each of the callbacks + * * resolution The number of byond ticks between each time you check if all callbacks are complete + */ /proc/callback_select(list/callbacks, list/callback_args, savereturns = TRUE, resolution = 1) if (!callbacks) return diff --git a/code/datums/components/material_container.dm b/code/datums/components/material_container.dm index 7adc634621..cc988544b8 100644 --- a/code/datums/components/material_container.dm +++ b/code/datums/components/material_container.dm @@ -10,25 +10,37 @@ */ /datum/component/material_container + /// The total amount of materials this material container contains var/total_amount = 0 + /// The maximum amount of materials this material container can contain var/max_amount - var/sheet_type + /// Map of material ref -> amount var/list/materials //Map of key = material ref | Value = amount + /// The list of materials that this material container can accept + var/list/allowed_materials var/show_on_examine var/disable_attackby var/list/allowed_typecache + /// The last main material that was inserted into this container var/last_inserted_id + /// Whether or not this material container allows specific amounts from sheets to be inserted var/precise_insertion = FALSE + /// A callback invoked before materials are inserted into this container var/datum/callback/precondition + /// A callback invoked after materials are inserted into this container var/datum/callback/after_insert /// Sets up the proper signals and fills the list of materials with the appropriate references. /datum/component/material_container/Initialize(list/mat_list, max_amt = 0, _show_on_examine = FALSE, list/allowed_types, datum/callback/_precondition, datum/callback/_after_insert, _disable_attackby) + if(!isatom(parent)) + return COMPONENT_INCOMPATIBLE + materials = list() max_amount = max(0, max_amt) show_on_examine = _show_on_examine disable_attackby = _disable_attackby + allowed_materials = mat_list || list() if(allowed_types) if(ispath(allowed_types) && allowed_types == /obj/item/stack) allowed_typecache = GLOB.typecache_stack @@ -38,14 +50,32 @@ precondition = _precondition after_insert = _after_insert - RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, .proc/OnAttackBy) - RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/OnExamine) + RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, .proc/on_attackby) + RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/on_examine) - for(var/mat in mat_list) //Make the assoc list ref | amount - var/datum/material/M = SSmaterials.GetMaterialRef(mat) - materials[M] = 0 + for(var/mat in mat_list) //Make the assoc list material reference -> amount + var/mat_ref = SSmaterials.GetMaterialRef(mat) + if(isnull(mat_ref)) + continue + var/mat_amt = mat_list[mat] + if(isnull(mat_amt)) + mat_amt = 0 + materials[mat_ref] += mat_amt + +/datum/component/material_container/Destroy(force, silent) + materials = null + allowed_typecache = null + // if(insertion_check) + // QDEL_NULL(insertion_check) + if(precondition) + QDEL_NULL(precondition) + if(after_insert) + QDEL_NULL(after_insert) + return ..() + +/datum/component/material_container/proc/on_examine(datum/source, mob/user, list/examine_list) + SIGNAL_HANDLER -/datum/component/material_container/proc/OnExamine(datum/source, mob/user, list/examine_list) if(show_on_examine) for(var/I in materials) var/datum/material/M = I @@ -54,7 +84,9 @@ examine_list += "It has [amt] units of [lowertext(M.name)] stored." /// Proc that allows players to fill the parent with mats -/datum/component/material_container/proc/OnAttackBy(datum/source, obj/item/I, mob/living/user) +/datum/component/material_container/proc/on_attackby(datum/source, obj/item/I, mob/living/user) + SIGNAL_HANDLER + var/list/tc = allowed_typecache if(disable_attackby) return @@ -63,48 +95,104 @@ if(I.item_flags & ABSTRACT) return if((I.flags_1 & HOLOGRAM_1) || (I.item_flags & NO_MAT_REDEMPTION) || (tc && !is_type_in_typecache(I, tc))) + // if(!(mat_container_flags & MATCONTAINER_SILENT)) to_chat(user, "[parent] won't accept [I]!") return . = COMPONENT_NO_AFTERATTACK var/datum/callback/pc = precondition if(pc && !pc.Invoke(user)) return - var/material_amount = get_item_material_amount(I) + var/material_amount = get_item_material_amount(I) //, mat_container_flags) if(!material_amount) to_chat(user, "[I] does not contain sufficient materials to be accepted by [parent].") return if((!precise_insertion || !GLOB.typecache_stack[I.type]) && !has_space(material_amount)) - to_chat(user, "[parent] has not enough space. Please remove materials from [parent] in order to insert more.") + to_chat(user, "[parent] is full. Please remove materials from [parent] in order to insert more.") return - user_insert(I, user) + user_insert(I, user) //, mat_container_flags) /// Proc used for when player inserts materials -/datum/component/material_container/proc/user_insert(obj/item/I, mob/living/user) +/datum/component/material_container/proc/user_insert(obj/item/I, mob/living/user, datum/component/remote_materials/remote = null) set waitfor = FALSE - var/requested_amount var/active_held = user.get_active_held_item() // differs from I when using TK - if(istype(I, /obj/item/stack) && precise_insertion) - var/atom/current_parent = parent + var/inserted = 0 + + //handle stacks specially + if(istype(I, /obj/item/stack)) + var/atom/current_parent = remote ? remote.parent : parent //is the user using a remote materials component? var/obj/item/stack/S = I - requested_amount = input(user, "How much do you want to insert?", "Inserting [S.singular_name]s") as num|null + + //try to get ammount to use + var/requested_amount + if(precise_insertion) + requested_amount = input(user, "How much do you want to insert?", "Inserting [S.singular_name]s") as num|null + else + requested_amount= S.amount + if(isnull(requested_amount) || (requested_amount <= 0)) return - if(QDELETED(I) || QDELETED(user) || QDELETED(src) || parent != current_parent || user.physical_can_use_topic(current_parent) < UI_INTERACTIVE || user.get_active_held_item() != active_held) + if(QDELETED(I) || QDELETED(user) || QDELETED(src) || user.get_active_held_item() != active_held) return - if(!user.temporarilyRemoveItemFromInventory(I)) - to_chat(user, "[I] is stuck to you and cannot be placed into [parent].") - return - var/inserted = insert_item(I, stack_amt = requested_amount) + //are we still in range after the user input? + if((remote ? remote.parent : parent) != current_parent || user.physical_can_use_topic(current_parent) < UI_INTERACTIVE) + return + inserted = insert_stack(S, requested_amount) + else + if(!user.temporarilyRemoveItemFromInventory(I)) + to_chat(user, "[I] is stuck to you and cannot be placed into [parent].") + return + inserted = insert_item(I) + qdel(I) + if(inserted) to_chat(user, "You insert a material total of [inserted] into [parent].") - qdel(I) if(after_insert) after_insert.Invoke(I, last_inserted_id, inserted) - else if(I == active_held) - user.put_in_active_hand(I) + if(remote && remote.after_insert) + remote.after_insert.Invoke(I, last_inserted_id, inserted) + +//Inserts a number of sheets from a stack, returns the amount of sheets used. +/datum/component/material_container/proc/insert_stack(obj/item/stack/S, amt, multiplier = 1) + if(isnull(amt)) + amt = S.amount + + if(amt <= 0) + return FALSE + + if(amt > S.amount) + amt = S.amount + + var/material_amt = get_item_material_amount(S) + if(!material_amt) + return FALSE + + //get max number of sheets we have room to add + var/mat_per_sheet = material_amt/S.amount + amt = min(amt, round((max_amount - total_amount) / (mat_per_sheet))) + if(!amt) + return FALSE + + //add the mats and keep track of how much was added + var/starting_total = total_amount + for(var/MAT in materials) + materials[MAT] += S.mats_per_unit[MAT] * amt * multiplier + total_amount += S.mats_per_unit[MAT] * amt * multiplier + var/total_added = total_amount - starting_total + + //update last_inserted_id with mat making up majority of the stack + var/primary_mat + var/max_mat_value = 0 + for(var/MAT in materials) + if(S.mats_per_unit[MAT] > max_mat_value) + max_mat_value = S.mats_per_unit[MAT] + primary_mat = MAT + last_inserted_id = primary_mat + + S.use(amt) + return total_added /// Proc specifically for inserting items, returns the amount of materials entered. -/datum/component/material_container/proc/insert_item(obj/item/I, var/multiplier = 1, stack_amt) +/datum/component/material_container/proc/insert_item(obj/item/I, var/multiplier = 1) if(QDELETED(I)) return FALSE @@ -117,16 +205,46 @@ last_inserted_id = insert_item_materials(I, multiplier) return material_amount +/** + * Inserts the relevant materials from an item into this material container. + * + * Arguments: + * - [source][/obj/item]: The source of the materials we are inserting. + * - multiplier: The multiplier for the materials being inserted. + * - breakdown_flags: The breakdown bitflags that will be used to retrieve the materials from the source + */ /datum/component/material_container/proc/insert_item_materials(obj/item/I, multiplier = 1) var/primary_mat var/max_mat_value = 0 - for(var/MAT in materials) - materials[MAT] += I.custom_materials[MAT] * multiplier - total_amount += I.custom_materials[MAT] * multiplier - if(I.custom_materials[MAT] > max_mat_value) + var/list/item_materials = I.custom_materials + for(var/MAT in item_materials) + if(!can_hold_material(MAT)) + continue + materials[MAT] += item_materials[MAT] * multiplier + total_amount += item_materials[MAT] * multiplier + if(item_materials[MAT] > max_mat_value) + max_mat_value = item_materials[MAT] primary_mat = MAT + return primary_mat +/** + * The default check for whether we can add materials to this material container. + * + * Arguments: + * - [mat][/atom/material]: The material we are checking for insertability. + */ +/datum/component/material_container/proc/can_hold_material(datum/material/mat) + if(mat in allowed_typecache) + return TRUE + if(istype(mat) && ((mat.id in allowed_typecache) || (mat.type in allowed_materials))) + allowed_materials += mat // This could get messy with passing lists by ref... but if you're doing that the list expansion is probably being taken care of elsewhere anyway... + return TRUE + // if(insertion_check?.Invoke(mat)) + // allowed_materials += mat + // return TRUE + return FALSE + /// For inserting an amount of material /datum/component/material_container/proc/insert_amount_mat(amt, var/datum/material/mat) if(!istype(mat)) @@ -135,6 +253,7 @@ var/total_amount_saved = total_amount if(mat) materials[mat] += amt + total_amount += amt else for(var/i in materials) materials[i] += amt diff --git a/code/datums/components/pellet_cloud.dm b/code/datums/components/pellet_cloud.dm index 6404be94c4..f88b9e1869 100644 --- a/code/datums/components/pellet_cloud.dm +++ b/code/datums/components/pellet_cloud.dm @@ -275,6 +275,11 @@ else target.visible_message("[target] is hit by a [proj_name][hit_part ? " in the [hit_part.name]" : ""]!", null, null, COMBAT_MESSAGE_RANGE, target) to_chat(target, "You're hit by a [proj_name][hit_part ? " in the [hit_part.name]" : ""]!") + + for(var/M in purple_hearts) + var/mob/living/martyr = M + if(martyr.stat == DEAD && martyr.client) + martyr.client.give_award(/datum/award/achievement/misc/lookoutsir, martyr) UnregisterSignal(parent, COMSIG_PARENT_PREQDELETED) if(queued_delete) qdel(parent) diff --git a/code/datums/components/remote_materials.dm b/code/datums/components/remote_materials.dm index 01038c11d3..b1d23ea3a8 100644 --- a/code/datums/components/remote_materials.dm +++ b/code/datums/components/remote_materials.dm @@ -15,13 +15,15 @@ handles linking back and forth. var/category var/allow_standalone var/local_size = INFINITY + var/datum/callback/after_insert -/datum/component/remote_materials/Initialize(category, mapload, allow_standalone = TRUE, force_connect = FALSE) +/datum/component/remote_materials/Initialize(category, mapload, allow_standalone = TRUE, force_connect = FALSE, _after_insert) if (!isatom(parent)) return COMPONENT_INCOMPATIBLE src.category = category src.allow_standalone = allow_standalone + after_insert = _after_insert RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, .proc/OnAttackBy) @@ -67,7 +69,7 @@ handles linking back and forth. /datum/material/plastic, ) - mat_container = parent.AddComponent(/datum/component/material_container, allowed_mats, local_size, allowed_types=/obj/item/stack) + mat_container = parent.AddComponent(/datum/component/material_container, allowed_mats, local_size, allowed_types=/obj/item/stack, _after_insert = after_insert) /datum/component/remote_materials/proc/set_local_size(size) local_size = size @@ -103,7 +105,7 @@ handles linking back and forth. return COMPONENT_NO_AFTERATTACK else if(silo && istype(I, /obj/item/stack)) - if(silo.remote_attackby(parent, user, I)) + if(silo.remote_attackby(parent, user, I, src)) return COMPONENT_NO_AFTERATTACK /datum/component/remote_materials/proc/on_hold() diff --git a/code/datums/components/storage/concrete/stack.dm b/code/datums/components/storage/concrete/stack.dm index a3f1e526a0..d7b118cee4 100644 --- a/code/datums/components/storage/concrete/stack.dm +++ b/code/datums/components/storage/concrete/stack.dm @@ -40,7 +40,8 @@ _S.add(can_insert) S.use(can_insert, TRUE) return TRUE - return ..(S.change_stack(null, can_insert), override) + I = S.split_stack(null, can_insert) + return ..() /datum/component/storage/concrete/stack/remove_from_storage(obj/item/I, atom/new_location) var/atom/real_location = real_location() diff --git a/code/datums/looping_sounds/machinery_sounds.dm b/code/datums/looping_sounds/machinery_sounds.dm index f7a3b46118..79810284cd 100644 --- a/code/datums/looping_sounds/machinery_sounds.dm +++ b/code/datums/looping_sounds/machinery_sounds.dm @@ -99,18 +99,18 @@ falloff_exponent = 5 volume = 50 */ -// /datum/looping_sound/computer -// start_sound = 'sound/machines/computer/computer_start.ogg' -// start_length = 7.2 SECONDS -// start_volume = 10 -// mid_sounds = list('sound/machines/computer/computer_mid1.ogg'=1, 'sound/machines/computer/computer_mid2.ogg'=1) -// mid_length = 1.8 SECONDS -// end_sound = 'sound/machines/computer/computer_end.ogg' -// end_volume = 10 -// volume = 2 -// falloff_exponent = 5 //Ultra quiet very fast -// extra_range = -12 -// falloff_distance = 1 //Instant falloff after initial tile +/datum/looping_sound/computer + start_sound = 'sound/machines/computer/computer_start.ogg' + start_length = 7.2 SECONDS + start_volume = 10 + mid_sounds = list('sound/machines/computer/computer_mid1.ogg'=1, 'sound/machines/computer/computer_mid2.ogg'=1) + mid_length = 1.8 SECONDS + end_sound = 'sound/machines/computer/computer_end.ogg' + end_volume = 10 + volume = 2 + falloff_exponent = 5 //Ultra quiet very fast + extra_range = -12 + falloff_distance = 1 //Instant falloff after initial tile // /datum/looping_sound/gravgen // mid_sounds = list('sound/machines/gravgen/gravgen_mid1.ogg'=1,'sound/machines/gravgen/gravgen_mid2.ogg'=1,'sound/machines/gravgen/gravgen_mid3.ogg'=1,'sound/machines/gravgen/gravgen_mid4.ogg'=1,) diff --git a/code/datums/looping_sounds/weather.dm b/code/datums/looping_sounds/weather.dm index d8ed8d123b..ea34fbb693 100644 --- a/code/datums/looping_sounds/weather.dm +++ b/code/datums/looping_sounds/weather.dm @@ -8,7 +8,7 @@ start_sound = 'sound/weather/ashstorm/outside/active_start.ogg' start_length = 130 end_sound = 'sound/weather/ashstorm/outside/active_end.ogg' - volume = 80 + volume = 60 /datum/looping_sound/active_inside_ashstorm mid_sounds = list( @@ -20,7 +20,7 @@ start_sound = 'sound/weather/ashstorm/inside/active_start.ogg' start_length = 130 end_sound = 'sound/weather/ashstorm/inside/active_end.ogg' - volume = 60 + volume = 20 /datum/looping_sound/weak_outside_ashstorm mid_sounds = list( diff --git a/code/datums/martial/cqc.dm b/code/datums/martial/cqc.dm index 96ba7bf965..4277cb1316 100644 --- a/code/datums/martial/cqc.dm +++ b/code/datums/martial/cqc.dm @@ -97,7 +97,7 @@ D.visible_message("[A] locks [D] into a restraining position!", \ "[A] locks you into a restraining position!") D.apply_damage(damage, STAMINA) - D.Stun(100) + D.Stun(10) restraining = TRUE addtimer(VARSET_CALLBACK(src, restraining, FALSE), 50, TIMER_UNIQUE) return TRUE @@ -175,7 +175,7 @@ return TRUE if(CHECK_MOBILITY(D, MOBILITY_MOVE) || !restraining) A.do_attack_animation(D, ATTACK_EFFECT_PUNCH) - if(damage >= stunthreshold) + if(damage >= stunthreshold) I = D.get_active_held_item() D.visible_message("[A] strikes [D]'s jaw with their hand!", \ "[A] strikes your jaw, disorienting you!") @@ -196,7 +196,8 @@ log_combat(A, D, "knocked out (Chokehold)(CQC)") D.visible_message("[A] puts [D] into a chokehold!", \ "[A] puts you into a chokehold!") - D.SetSleeping(400) + if(D.silent <= 10) + D.silent = clamp(D.silent + 10, 0, 10) restraining = FALSE if(A.grab_state < GRAB_NECK) A.setGrabState(GRAB_NECK) @@ -213,7 +214,7 @@ to_chat(usr, "Slam: Grab Harm. Slam opponent into the ground, knocking them down.") to_chat(usr, "CQC Kick: Harm Harm. Knocks opponent away. Knocks out stunned or knocked down opponents.") - to_chat(usr, "Restrain: Grab Grab. Locks opponents into a restraining position, disarm to knock them out with a chokehold.") + to_chat(usr, "Restrain: Grab Grab. Locks opponents into a restraining position, disarm to mute them with a chokehold.") to_chat(usr, "Pressure: Disarm Grab. Decent stamina damage.") to_chat(usr, "Consecutive CQC: Disarm Disarm Harm. Mainly offensive move, huge damage and decent stamina damage.") diff --git a/code/datums/materials/_material.dm b/code/datums/materials/_material.dm index 5148aab4ac..658f22a107 100644 --- a/code/datums/materials/_material.dm +++ b/code/datums/materials/_material.dm @@ -1,15 +1,24 @@ /*! Material datum + Simple datum which is instanced once per type and is used for every object of said material. It has a variety of variables that define behavior. Subtyping from this makes it easier to create your own materials. + */ /datum/material + /// What the material is referred to as IC. var/name = "material" + /// A short description of the material. Not used anywhere, yet... var/desc = "its..stuff." + /// What the material is indexed by in the SSmaterials.materials list. Defaults to the type of the material. + var/id + ///Base color of the material, is used for greyscale. Item isn't changed in color if this is null. var/color ///Base alpha of the material, is used for greyscale icons. var/alpha + ///Bitflags that influence how SSmaterials handles this material. + // var/init_flags = MATERIAL_INIT_MAPLOAD ///Materials "Traits". its a map of key = category | Value = Bool. Used to define what it can be used for var/list/categories = list() ///The type of sheet this material creates. This should be replaced as soon as possible by greyscale sheets @@ -22,7 +31,7 @@ Simple datum which is instanced once per type and is used for every object of sa var/value_per_unit = 0 ///Armor modifiers, multiplies an items normal armor vars by these amounts. var/armor_modifiers = list("melee" = 1, "bullet" = 1, "laser" = 1, "energy" = 1, "bomb" = 1, "bio" = 1, "rad" = 1, "fire" = 1, "acid" = 1) - ///How beautiful is this material per unit? + ///How beautiful is this material per unit. var/beauty_modifier = 0 ///Can be used to override the sound items make, lets add some SLOSHing. var/item_sound_override @@ -30,14 +39,31 @@ Simple datum which is instanced once per type and is used for every object of sa var/turf_sound_override ///what texture icon state to overlay var/texture_layer_icon_state - ///a cached filter for the texture icon + ///a cached icon for the texture filter var/cached_texture_filter + ///What type of shard the material will shatter to + var/obj/item/shard_type + + +/** Handles initializing the material. + * + * Arugments: + * - _id: The ID the material should use. Overrides the existing ID. + */ +/datum/material/proc/Initialize(_id, ...) + if(_id) + id = _id + else if(isnull(id)) + id = type + + if(texture_layer_icon_state) + cached_texture_filter = icon('icons/materials/composite.dmi', texture_layer_icon_state) + + return TRUE /datum/material/New() . = ..() - if(texture_layer_icon_state) - var/texture_icon = icon('icons/materials/composite.dmi', texture_layer_icon_state) - cached_texture_filter = filter(type="layer", icon=texture_icon, blend_mode = BLEND_INSET_OVERLAY) + Initialize() ///This proc is called when the material is added to an object. /datum/material/proc/on_applied(atom/source, amount, material_flags) @@ -48,18 +74,20 @@ Simple datum which is instanced once per type and is used for every object of sa source.alpha = alpha if(texture_layer_icon_state) ADD_KEEP_TOGETHER(source, MATERIAL_SOURCE(src)) - source.filters += cached_texture_filter + source.add_filter("material_texture_[name]",1,layering_filter(icon=cached_texture_filter,blend_mode=BLEND_INSET_OVERLAY)) + if(alpha < 255) + source.opacity = FALSE if(material_flags & MATERIAL_ADD_PREFIX) source.name = "[name] [source.name]" - if(beauty_modifier) - addtimer(CALLBACK(source, /datum.proc/_AddElement, list(/datum/element/beauty, beauty_modifier * amount)), 0) + // if(beauty_modifier) returnign in hardsync2 if i ever port ebeauty cmp + // addtimer(CALLBACK(source, /datum.proc/_AddElement, list(/datum/element/beauty, beauty_modifier * amount)), 0) if(istype(source, /obj)) //objs on_applied_obj(source, amount, material_flags) - else if(isturf(source, /turf)) //turfs + if(istype(source, /turf)) //turfs on_applied_turf(source, amount, material_flags) source.mat_update_desc(src) @@ -67,8 +95,9 @@ Simple datum which is instanced once per type and is used for every object of sa ///This proc is called when a material updates an object's description /atom/proc/mat_update_desc(/datum/material/mat) return + ///This proc is called when the material is added to an object specifically. -/datum/material/proc/on_applied_obj(var/obj/o, amount, material_flags) +/datum/material/proc/on_applied_obj(obj/o, amount, material_flags) if(material_flags & MATERIAL_AFFECT_STATISTICS) var/new_max_integrity = CEILING(o.max_integrity * integrity_modifier, 1) o.modify_max_integrity(new_max_integrity) @@ -92,43 +121,73 @@ Simple datum which is instanced once per type and is used for every object of sa I.hitsound = item_sound_override I.usesound = item_sound_override I.throwhitsound = item_sound_override + // I.mob_throw_hit_sound = item_sound_override + // I.equip_sound = item_sound_override + // I.pickup_sound = item_sound_override + // I.drop_sound = item_sound_override -/datum/material/proc/on_applied_turf(var/turf/T, amount, material_flags) +/datum/material/proc/on_applied_turf(turf/T, amount, material_flags) if(isopenturf(T)) - if(!turf_sound_override) - return - var/turf/open/O = T - O.footstep = turf_sound_override - O.barefootstep = turf_sound_override - O.clawfootstep = turf_sound_override - O.heavyfootstep = turf_sound_override + if(turf_sound_override) + var/turf/open/O = T + O.footstep = turf_sound_override + O.barefootstep = turf_sound_override + O.clawfootstep = turf_sound_override + O.heavyfootstep = turf_sound_override + // if(alpha < 255) + // T.AddElement(/datum/element/turf_z_transparency, TRUE) + return ///This proc is called when the material is removed from an object. -/datum/material/proc/on_removed(atom/source, material_flags) +/datum/material/proc/on_removed(atom/source, amount, material_flags) if(material_flags & MATERIAL_COLOR) //Prevent changing things with pre-set colors, to keep colored toolboxes their looks for example if(color) source.remove_atom_colour(FIXED_COLOUR_PRIORITY, color) - source.alpha = initial(source.alpha) if(texture_layer_icon_state) - source.filters -= cached_texture_filter + source.remove_filter("material_texture_[name]") REMOVE_KEEP_TOGETHER(source, MATERIAL_SOURCE(src)) + source.alpha = initial(source.alpha) if(material_flags & MATERIAL_ADD_PREFIX) source.name = initial(source.name) - if(istype(source, /obj)) //objs - on_removed_obj(source, material_flags) + // if(beauty_modifier) //component/beauty/InheritComponent() will handle the removal. + // addtimer(CALLBACK(source, /datum.proc/_AddElement, list(/datum/element/beauty, -beauty_modifier * amount)), 0) - else if(istype(source, /turf)) //turfs - on_removed_turf(source, material_flags) + if(istype(source, /obj)) //objs + on_removed_obj(source, amount, material_flags) + + if(istype(source, /turf)) //turfs + on_removed_turf(source, amount, material_flags) ///This proc is called when the material is removed from an object specifically. -/datum/material/proc/on_removed_obj(obj/o, material_flags) +/datum/material/proc/on_removed_obj(obj/o, amount, material_flags) if(material_flags & MATERIAL_AFFECT_STATISTICS) var/new_max_integrity = initial(o.max_integrity) o.modify_max_integrity(new_max_integrity) o.force = initial(o.force) o.throwforce = initial(o.throwforce) -/datum/material/proc/on_removed_turf(turf/T, material_flags) - return +/datum/material/proc/on_removed_turf(turf/T, amount, material_flags) + // if(alpha) + // RemoveElement(/datum/element/turf_z_transparency, FALSE) + +/** + * This proc is called when the mat is found in an item that's consumed by accident. see /obj/item/proc/on_accidental_consumption. + * Arguments + * * M - person consuming the mat + * * S - (optional) item the mat is contained in (NOT the item with the mat itself) + */ +/datum/material/proc/on_accidental_mat_consumption(mob/living/carbon/M, obj/item/S) + return FALSE + +/** Returns the composition of this material. + * + * Mostly used for alloys when breaking down materials. + * + * Arguments: + * - amount: The amount of the material to break down. + * - breakdown_flags: Some flags dictating how exactly this material is being broken down. + */ +/datum/material/proc/return_composition(amount=1, breakdown_flags=NONE) + return list((src) = amount) // Yes we need the parenthesis, without them BYOND stringifies src into "src" and things break. diff --git a/code/datums/shuttles.dm b/code/datums/shuttles.dm index e2e6a05b08..62f8cef5dc 100644 --- a/code/datums/shuttles.dm +++ b/code/datums/shuttles.dm @@ -8,12 +8,16 @@ var/description var/prerequisites var/admin_notes - + /// How much does this shuttle cost the cargo budget to purchase? Put in terms of CARGO_CRATE_VALUE to properly scale the cost with the current balance of cargo's income. var/credit_cost = INFINITY + /// Can the be legitimately purchased by the station? Used by hardcoded or pre-mapped shuttles like the lavaland or cargo shuttle. var/can_be_bought = TRUE + /// If set, overrides default movement_force on shuttle + var/list/movement_force var/port_x_offset var/port_y_offset + var/extra_desc = "" /datum/map_template/shuttle/proc/prerequisites_met() return TRUE @@ -23,7 +27,7 @@ mappath = "[prefix][shuttle_id].dmm" . = ..() -/datum/map_template/shuttle/preload_size(path = mappath, force_cache = FALSE) +/datum/map_template/shuttle/preload_size(path, force_cache) . = ..(path, TRUE) // Done this way because we still want to know if someone actualy wanted to cache the map if(!cached_map) return @@ -64,6 +68,9 @@ continue if(length(place.baseturfs) < 2) // Some snowflake shuttle shit continue + // var/list/sanity = place.baseturfs.Copy() // we do not have new baseturfs yet + // sanity.Insert(3, /turf/baseturf_skipover/shuttle) + // place.baseturfs = baseturfs_string_list(sanity, place) place.baseturfs.Insert(3, /turf/baseturf_skipover/shuttle) for(var/obj/docking_port/mobile/port in place) @@ -93,6 +100,7 @@ port.dwidth = port_y_offset - 1 port.dheight = width - port_x_offset + // these three for loops are cit specific. for(var/obj/structure/closet/closet in place) if(closet.anchorable) closet.anchored = TRUE @@ -104,11 +112,10 @@ rack.AddComponent(/datum/component/magnetic_catch) //Whatever special stuff you want -/datum/map_template/shuttle/proc/post_load(obj/docking_port/mobile/M) - return - -/datum/map_template/shuttle/proc/on_bought() - return +/datum/map_template/shuttle/post_load(obj/docking_port/mobile/M) + if(movement_force) + M.movement_force = movement_force.Copy() + M.linkup() /datum/map_template/shuttle/emergency port_id = "emergency" @@ -117,6 +124,7 @@ /datum/map_template/shuttle/cargo port_id = "cargo" name = "Base Shuttle Template (Cargo)" + can_be_bought = FALSE /datum/map_template/shuttle/ferry port_id = "ferry" @@ -137,10 +145,6 @@ port_id = "mining_common" can_be_bought = FALSE -/datum/map_template/shuttle/cargo - port_id = "cargo" - can_be_bought = FALSE - /datum/map_template/shuttle/arrival port_id = "arrival" can_be_bought = FALSE @@ -189,21 +193,23 @@ name = "Backup Shuttle" can_be_bought = FALSE -/datum/map_template/shuttle/emergency/airless - suffix = "airless" +/datum/map_template/shuttle/emergency/construction + suffix = "construction" name = "Build your own shuttle kit" - description = "Save money by building your own shuttle! The chassis will dock upon purchase, but launch will have to be authorized as usual via shuttle call. Interior and lighting not included." + description = "For the enterprising shuttle engineer! The chassis will dock upon purchase, but launch will have to be authorized as usual via shuttle call. Comes stocked with construction materials. Unlocks the ability to buy shuttle engine crates from cargo." admin_notes = "No brig, no medical facilities, just an empty box." credit_cost = -7500 -/datum/map_template/shuttle/emergency/airless/prerequisites_met() +/datum/map_template/shuttle/emergency/construction/prerequisites_met() // first 10 minutes only return world.time - SSticker.round_start_time < 6000 -/datum/map_template/shuttle/emergency/airless/on_bought() - //enable buying engines from cargo - var/datum/supply_pack/P = SSshuttle.supply_packs[/datum/supply_pack/engineering/shuttle_engine] - P.special_enabled = TRUE +// this is broken and does not work. Thanks TG +// /datum/map_template/shuttle/emergency/airless/post_load() +// . = ..() +// //enable buying engines from cargo +// var/datum/supply_pack/P = SSshuttle.supply_packs[/datum/supply_pack/engineering/shuttle_engine] +// P.special_enabled = TRUE /datum/map_template/shuttle/emergency/asteroid @@ -220,6 +226,13 @@ Has medical facilities." credit_cost = 5000 +// /datum/map_template/shuttle/emergency/pod +// suffix = "pod" +// name = "Emergency Pods" +// description = "We did not expect an evacuation this quickly. All we have available is two escape pods." +// admin_notes = "For player punishment." +// can_be_bought = FALSE + /datum/map_template/shuttle/emergency/russiafightpit suffix = "russiafightpit" name = "Mother Russia Bleeds" @@ -230,9 +243,10 @@ /datum/map_template/shuttle/emergency/meteor suffix = "meteor" name = "Asteroid With Engines Strapped To It" - description = "A hollowed out asteroid with engines strapped to it. Due to its size and difficulty in steering it, this shuttle may damage the docking area." + description = "A hollowed out asteroid with engines strapped to it, the hollowing procedure makes it very difficult to hijack but is very expensive. Due to its size and difficulty in steering it, this shuttle may damage the docking area." admin_notes = "This shuttle will likely crush escape, killing anyone there." credit_cost = -5000 + movement_force = list("KNOCKDOWN" = 3, "THROW" = 2) /datum/map_template/shuttle/emergency/luxury suffix = "luxury" @@ -247,18 +261,30 @@ description = "The glorious results of centuries of plasma research done by Nanotrasen employees. This is the reason why you are here. Get on and dance like you're on fire, burn baby burn!" admin_notes = "Flaming hot. The main area has a dance machine as well as plasma floor tiles that will be ignited by players every single time." credit_cost = 10000 + // can_be_bought = FALSE -/datum/map_template/shuttle/emergency/arena - suffix = "arena" - name = "The Arena" - description = "The crew must pass through an otherworldy arena to board this shuttle. Expect massive casualties. The source of the Bloody Signal must be tracked down and eliminated to unlock this shuttle." - admin_notes = "RIP AND TEAR." - credit_cost = 10000 +// /datum/map_template/shuttle/emergency/arena +// suffix = "arena" +// name = "The Arena" +// description = "The crew must pass through an otherworldy arena to board this shuttle. Expect massive casualties. The source of the Bloody Signal must be tracked down and eliminated to unlock this shuttle." +// admin_notes = "RIP AND TEAR." +// credit_cost = 10000 +// /// Whether the arena z-level has been created +// var/arena_loaded = FALSE -/datum/map_template/shuttle/emergency/arena/prerequisites_met() - if("bubblegum" in SSshuttle.shuttle_purchase_requirements_met) - return TRUE - return FALSE +// /datum/map_template/shuttle/emergency/arena/prerequisites_met() +// return SSshuttle.shuttle_purchase_requirements_met["bubblegum"] + +// /datum/map_template/shuttle/emergency/arena/post_load(obj/docking_port/mobile/M) +// . = ..() +// if(!arena_loaded) +// arena_loaded = TRUE +// var/datum/map_template/arena/arena_template = new() +// arena_template.load_new_z() + +// /datum/map_template/arena +// name = "The Arena" +// mappath = "_maps/templates/the_arena.dmm" /datum/map_template/shuttle/emergency/birdboat suffix = "birdboat" @@ -272,6 +298,13 @@ credit_cost = 2000 description = "The gold standard in emergency exfiltration, this tried and true design is equipped with everything the crew needs for a safe flight home." +// /datum/map_template/shuttle/emergency/donut +// suffix = "donut" +// name = "Donutstation Emergency Shuttle" +// description = "The perfect spearhead for any crude joke involving the station's shape, this shuttle supports a separate containment cell for prisoners and a compact medical wing." +// admin_notes = "Has airlocks on both sides of the shuttle and will probably intersect near the front on some stations that build past departures." +// credit_cost = 2500 + /datum/map_template/shuttle/emergency/clown suffix = "clown" name = "Snappop(tm)!" @@ -316,7 +349,9 @@ credit_cost = -1000 description = "Due to a lack of functional emergency shuttles, we bought this second hand from a scrapyard and pressed it into service. Please do not lean too heavily on the exterior windows, they are fragile." admin_notes = "An abomination with no functional medbay, sections missing, and some very fragile windows. Surprisingly airtight." + movement_force = list("KNOCKDOWN" = 3, "THROW" = 2) +// CIT SPECIFIC /datum/map_template/shuttle/emergency/syndicate suffix = "syndicate" name = "Syndicate GM Battlecruiser" @@ -325,9 +360,7 @@ admin_notes = "An emag exclusive, stocked with syndicate equipment and turrets that will target any simplemob." /datum/map_template/shuttle/emergency/syndicate/prerequisites_met() - if("emagged" in SSshuttle.shuttle_purchase_requirements_met) - return TRUE - return FALSE + return SSshuttle.shuttle_purchase_requirements_met["emagged"] /datum/map_template/shuttle/emergency/narnar suffix = "narnar" @@ -335,6 +368,10 @@ description = "Looks like this shuttle may have wandered into the darkness between the stars on route to the station. Let's not think too hard about where all the bodies came from." admin_notes = "Contains real cult ruins, mob eyeballs, and inactive constructs. Cult mobs will automatically be sentienced by fun balloon. \ Cloning pods in 'medbay' area are showcases and nonfunctional." + credit_cost = 6667 ///The joke is the number so no defines + +/datum/map_template/shuttle/emergency/narnar/prerequisites_met() + return SSshuttle.shuttle_purchase_requirements_met["narsie"] /datum/map_template/shuttle/emergency/pubby suffix = "pubby" @@ -354,7 +391,7 @@ /datum/map_template/shuttle/emergency/supermatter suffix = "supermatter" name = "Hyperfractal Gigashuttle" - description = "(Emag only) \"I dunno, this seems kinda needlessly complicated.\"\n\ + description = "\"I dunno, this seems kinda needlessly complicated.\"\n\ \"This shuttle has very a very high safety record, according to CentCom Officer Cadet Yins.\"\n\ \"Are you sure?\"\n\ \"Yes, it has a safety record of N-A-N, which is apparently larger than 100%.\"" @@ -363,19 +400,19 @@ It does, however, still dust anything on contact, emits high levels of radiation, and induce hallucinations in anyone looking at it without protective goggles. \ Emitters spawn powered on, expect admin notices, they are harmless." credit_cost = 15000 + movement_force = list("KNOCKDOWN" = 3, "THROW" = 2) /datum/map_template/shuttle/emergency/supermatter/prerequisites_met() - if("emagged" in SSshuttle.shuttle_purchase_requirements_met) - return TRUE - return FALSE - + return SSshuttle.shuttle_purchase_requirements_met["emagged"] /datum/map_template/shuttle/emergency/imfedupwiththisworld suffix = "imfedupwiththisworld" name = "Oh, Hi Daniel" description = "How was space work today? Oh, pretty good. We got a new space station and the company will make a lot of money. What space station? I cannot tell you; it's space confidential. \ Aw, come space on. Why not? No, I can't. Anyway, how is your space roleplay life?" admin_notes = "Tiny, with a single airlock and wooden walls. What could go wrong?" + // can_be_bought = FALSE credit_cost = -5000 + movement_force = list("KNOCKDOWN" = 3, "THROW" = 2) /datum/map_template/shuttle/emergency/goon suffix = "goon" @@ -383,6 +420,14 @@ description = "The Nanotrasen Emergency Shuttle Port(NES Port for short) is a shuttle used at other less known Nanotrasen facilities and has a more open inside for larger crowds, but fewer onboard shuttle facilities." credit_cost = 500 +// /datum/map_template/shuttle/emergency/rollerdome +// suffix = "rollerdome" +// name = "Uncle Pete's Rollerdome" +// description = "Developed by a member of Nanotrasen's R&D crew that claims to have travelled from the year 2028. +// He says this shuttle is based off an old entertainment complex from the 1990s, though our database has no records on anything pertaining to that decade." +// admin_notes = "ONLY NINETIES KIDS REMEMBER. Uses the fun balloon and drone from the Emergency Bar." +// credit_cost = 500 * 5 + /datum/map_template/shuttle/emergency/wabbajack suffix = "wabbajack" name = "NT Lepton Violet" @@ -398,6 +443,7 @@ description = "On the smaller size with a modern design, this shuttle is for the crew who like the cosier things, while still being able to stretch their legs." credit_cost = 1000 +// CIT SPECIFIC /datum/map_template/shuttle/emergency/gorilla suffix = "gorilla" name = "Gorilla Cargo Freighter" @@ -405,11 +451,17 @@ credit_cost = 2000 /datum/map_template/shuttle/emergency/gorilla/prerequisites_met() - if("emagged" in SSshuttle.shuttle_purchase_requirements_met) - return TRUE - return FALSE + return SSshuttle.shuttle_purchase_requirements_met["emagged"] -/datum/map_template/shuttle/emergency/cruise + +// /datum/map_template/shuttle/emergency/cruise +// suffix = "cruise" +// name = "The NTSS Independence" +// description = "Ordinarily reserved for special functions and events, the Cruise Shuttle Independence can bring a summery cheer to your next station evacuation for a 'modest' fee!" +// admin_notes = "This motherfucker is BIG. You might need to force dock it." +// credit_cost = 8000 + +/datum/map_template/shuttle/emergency/monkey suffix = "nature" name = "Dynamic Environmental Interaction Shuttle" description = "A large shuttle with a center biodome that is flourishing with life. Frolick with the monkeys! (Extra monkeys are stored on the bridge.)" @@ -441,7 +493,7 @@ /datum/map_template/shuttle/ferry/fancy suffix = "fancy" name = "fancy transport ferry" - description = "At some point, someone upgraded the ferry to have fancier flooring... and less seats." + description = "At some point, someone upgraded the ferry to have fancier flooring... and fewer seats." /datum/map_template/shuttle/ferry/kilo suffix = "kilo" @@ -464,6 +516,14 @@ suffix = "cere" name = "NT Construction Vessel" +// /datum/map_template/shuttle/whiteship/kilo +// suffix = "kilo" +// name = "NT Mining Shuttle" + +// /datum/map_template/shuttle/whiteship/donut +// suffix = "donut" +// name = "NT Long-Distance Bluespace Jumper" + /datum/map_template/shuttle/whiteship/delta suffix = "delta" name = "NT Frigate" @@ -476,10 +536,6 @@ suffix = "cog" name = "NT Prisoner Transport" -/datum/map_template/shuttle/cargo/box - suffix = "box" - name = "supply shuttle (Box)" - /datum/map_template/shuttle/cargo/kilo suffix = "kilo" name = "supply shuttle (Kilo)" @@ -488,6 +544,14 @@ suffix = "birdboat" name = "supply shuttle (Birdboat)" +// /datum/map_template/shuttle/cargo/donut +// suffix = "donut" +// name = "supply shuttle (Donut)" + +// /datum/map_template/shuttle/cargo/pubby +// suffix = "pubby" +// name = "supply shuttle (Pubby)" + /datum/map_template/shuttle/emergency/delta suffix = "delta" name = "Delta Station Emergency Shuttle" @@ -497,11 +561,24 @@ /datum/map_template/shuttle/emergency/raven suffix = "raven" - name = "CentCom Raven Battlecruiser" - description = "The CentCom Raven Battlecruiser is currently docked at the CentCom ship bay awaiting a mission, this Battlecruiser has been reassigned as an emergency escape shuttle for currently unknown reasons. The CentCom Raven Battlecruiser should comfortably fit a medium to large crew size crew and is complete with all required facitlities including a top of the range CentCom Medical Bay." - admin_notes = "Comes with turrets that will target any simplemob." + name = "CentCom Raven Cruiser" + description = "The CentCom Raven Cruiser is a former high-risk salvage vessel, now repurposed into an emergency escape shuttle. \ + Once first to the scene to pick through warzones for valuable remains, it now serves as an excellent escape option for stations under heavy fire from outside forces. \ + This escape shuttle boasts shields and numerous anti-personnel turrets guarding its perimeter to fend off meteors and enemy boarding attempts." + admin_notes = "Comes with turrets that will target anything without the neutral faction (nuke ops, xenos etc, but not pets)." credit_cost = 12500 +// /datum/map_template/shuttle/emergency/zeta +// suffix = "zeta" +// name = "Tr%nPo2r& Z3TA" +// description = "A glitch appears on your monitor, flickering in and out of the options laid before you. +// It seems strange and alien, you may need a special technology to access the signal.." +// admin_notes = "Has alien surgery tools, and a void core that provides unlimited power." +// credit_cost = CARGO_CRATE_VALUE * 16 + +// /datum/map_template/shuttle/emergency/zeta/prerequisites_met() +// return SSshuttle.shuttle_purchase_requirements_met[SHUTTLE_UNLOCK_ALIENTECH] + /datum/map_template/shuttle/emergency/cog suffix = "cog" name = "NES Classic" @@ -524,18 +601,22 @@ suffix = "box" name = "labour shuttle (Box)" -/datum/map_template/shuttle/labour/kilo - suffix = "kilo" - name = "labour shuttle (Kilo)" - /datum/map_template/shuttle/labour/cog suffix = "cog" name = "labour shuttle (Cog)" +// /datum/map_template/shuttle/arrival/donut +// suffix = "donut" +// name = "arrival shuttle (Donut)" + /datum/map_template/shuttle/infiltrator/basic suffix = "basic" name = "basic syndicate infiltrator" +// /datum/map_template/shuttle/infiltrator/advanced +// suffix = "advanced" +// name = "advanced syndicate infiltrator" + /datum/map_template/shuttle/cargo/delta suffix = "delta" name = "cargo ferry (Delta)" @@ -548,17 +629,25 @@ suffix = "kilo" name = "mining shuttle (Kilo)" +// /datum/map_template/shuttle/mining/large +// suffix = "large" +// name = "mining shuttle (Large)" + /datum/map_template/shuttle/labour/delta suffix = "delta" name = "labour shuttle (Delta)" +/datum/map_template/shuttle/labour/kilo + suffix = "kilo" + name = "labour shuttle (Kilo)" + /datum/map_template/shuttle/mining_common/meta suffix = "meta" name = "lavaland shuttle (Meta)" -/datum/map_template/shuttle/labour/kilo - suffix = "kilo" - name = "labour shuttle (Kilo)" +// /datum/map_template/shuttle/mining_common/kilo +// suffix = "kilo" +// name = "lavaland shuttle (Kilo)" /datum/map_template/shuttle/arrival/delta suffix = "delta" @@ -608,6 +697,18 @@ suffix = "default" name = "pirate ship (Default)" +/datum/map_template/shuttle/hunter/space_cop + suffix = "space_cop" + name = "Police Spacevan" + +/datum/map_template/shuttle/hunter/russian + suffix = "russian" + name = "Russian Cargo Ship" + +/datum/map_template/shuttle/hunter/bounty + suffix = "bounty" + name = "Bounty Hunter Ship" + /datum/map_template/shuttle/ruin/caravan_victim suffix = "caravan_victim" name = "Small Freighter" @@ -631,15 +732,3 @@ /datum/map_template/shuttle/snowdin/excavation suffix = "excavation" name = "Snowdin Excavation Elevator" - -/datum/map_template/shuttle/hunter/space_cop - suffix = "space_cop" - name = "Police Spacevan" - -/datum/map_template/shuttle/hunter/russian - suffix = "russian" - name = "Russian Cargo Ship" - -/datum/map_template/shuttle/hunter/bounty - suffix = "bounty" - name = "Bounty Hunter Ship" diff --git a/code/datums/tgs_event_handler.dm b/code/datums/tgs_event_handler.dm index 731be64183..434450b9be 100644 --- a/code/datums/tgs_event_handler.dm +++ b/code/datums/tgs_event_handler.dm @@ -5,8 +5,8 @@ switch(event_code) if(TGS_EVENT_REBOOT_MODE_CHANGE) var/list/reboot_mode_lookup = list ("[TGS_REBOOT_MODE_NORMAL]" = "be normal", "[TGS_REBOOT_MODE_SHUTDOWN]" = "shutdown the server", "[TGS_REBOOT_MODE_RESTART]" = "hard restart the server") - var old_reboot_mode = args[2] - var new_reboot_mode = args[3] + var/old_reboot_mode = args[2] + var/new_reboot_mode = args[3] message_admins("TGS: Reboot will no longer [reboot_mode_lookup["[old_reboot_mode]"]], it will instead [reboot_mode_lookup["[new_reboot_mode]"]]") if(TGS_EVENT_PORT_SWAP) message_admins("TGS: Changing port from [world.port] to [args[2]]") @@ -28,12 +28,14 @@ var/datum/tgs_version/old_version = world.TgsVersion() var/datum/tgs_version/new_version = args[2] if(!old_version.Equals(new_version)) - to_chat(world, "TGS updated to v[old_version.deprefixed_parameter]") + to_chat(world, "TGS updated to v[new_version.deprefixed_parameter]") else message_admins("TGS: Back online") if(reattach_timer) deltimer(reattach_timer) reattach_timer = null + if(TGS_EVENT_WATCHDOG_SHUTDOWN) + to_chat_immediate(world, "Server is shutting down!") /datum/tgs_event_handler/impl/proc/LateOnReattach() message_admins("Warning: TGS hasn't notified us of it coming back for a full minute! Is there a problem?") diff --git a/code/datums/traits/negative.dm b/code/datums/traits/negative.dm index cce138e82c..38007023b5 100644 --- a/code/datums/traits/negative.dm +++ b/code/datums/traits/negative.dm @@ -153,7 +153,7 @@ GLOBAL_LIST_EMPTY(family_heirlooms) name = "Light Sensitivity" desc = "Bright lights irritate you. Your eyes start to water, your skin feels itchy against the photon radiation, and your hair gets dry and frizzy. Maybe it's a medical condition. If only Nanotrasen was more considerate of your needs..." value = -1 - gain_text = "The safty of light feels off..." + gain_text = "Bright lights seem irritating." lose_text = "Enlightening." medical_record_text = "Despite my warnings, the patient refuses turn on the lights, only to end up rolling down a full flight of stairs and into the cellar." diff --git a/code/datums/view.dm b/code/datums/view.dm index 8eb06c2bd2..5610fb040e 100644 --- a/code/datums/view.dm +++ b/code/datums/view.dm @@ -83,8 +83,6 @@ /datum/viewData/proc/apply() chief.change_view(getView()) safeApplyFormat() - if(chief.prefs.auto_fit_viewport) - chief.fit_viewport() /datum/viewData/proc/supress() is_suppressed = TRUE diff --git a/code/datums/weather/weather_types/radiation_storm.dm b/code/datums/weather/weather_types/radiation_storm.dm index 4638508d1c..337be3e005 100644 --- a/code/datums/weather/weather_types/radiation_storm.dm +++ b/code/datums/weather/weather_types/radiation_storm.dm @@ -22,7 +22,7 @@ target_trait = ZTRAIT_STATION immunity_type = "rad" - + var/radiation_intensity = 100 /datum/weather/rad_storm/telegraph() diff --git a/code/datums/world_topic.dm b/code/datums/world_topic.dm index 7a1f0f6980..946090c571 100644 --- a/code/datums/world_topic.dm +++ b/code/datums/world_topic.dm @@ -90,7 +90,7 @@ if(!is_new_ckey) log_admin("AUTO BUNKER: [ckeytobypass] given access (incoming comms from [sender]).") message_admins("AUTO BUNKER: [ckeytobypass] given access (incoming comms from [sender]).") - send2irc("Panic Bunker", "AUTO BUNKER: [ckeytobypass] given access (incoming comms from [sender]).") + send2adminchat("Panic Bunker", "AUTO BUNKER: [ckeytobypass] given access (incoming comms from [sender]).") return "Success" /datum/world_topic/ahelp_relay @@ -106,8 +106,8 @@ /datum/world_topic/comms_console/Run(list/input, addr) minor_announce(input["message"], "Incoming message from [input["message_sender"]]") - for(var/obj/machinery/computer/communications/CM in GLOB.machines) - CM.overrideCooldown() + for(var/obj/machinery/computer/communications/console in GLOB.machines) + console.override_cooldown() /datum/world_topic/news_report keyword = "News_Report" diff --git a/code/game/atoms.dm b/code/game/atoms.dm index bd2e515562..18674cdb17 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -885,6 +885,9 @@ VV_DROPDOWN_OPTION(VV_HK_ADD_REAGENT, "Add Reagent") VV_DROPDOWN_OPTION(VV_HK_TRIGGER_EMP, "EMP Pulse") VV_DROPDOWN_OPTION(VV_HK_TRIGGER_EXPLOSION, "Explosion") + // VV_DROPDOWN_OPTION(VV_HK_RADIATE, "Radiate") + VV_DROPDOWN_OPTION(VV_HK_EDIT_FILTERS, "Edit Filters") + // VV_DROPDOWN_OPTION(VV_HK_ADD_AI, "Add AI controller") /atom/vv_do_topic(list/href_list) . = ..() @@ -928,6 +931,9 @@ var/newname = input(usr, "What do you want to rename this to?", "Automatic Rename") as null|text if(newname) vv_auto_rename(newname) + if(href_list[VV_HK_EDIT_FILTERS] && check_rights(R_VAREDIT)) + var/client/C = usr.client + C?.open_filter_editor(src) /atom/vv_get_header() . = ..() @@ -1148,7 +1154,6 @@ victim.log_message(message, LOG_ATTACK, color="blue") -// Filter stuff /atom/proc/add_filter(name,priority,list/params) LAZYINITLIST(filter_data) var/list/p = params.Copy() @@ -1164,26 +1169,64 @@ var/list/arguments = data.Copy() arguments -= "priority" filters += filter(arglist(arguments)) + UNSETEMPTY(filter_data) + +/atom/proc/transition_filter(name, time, list/new_params, easing, loop) + var/filter = get_filter(name) + if(!filter) + return + + var/list/old_filter_data = filter_data[name] + + var/list/params = old_filter_data.Copy() + for(var/thing in new_params) + params[thing] = new_params[thing] + + animate(filter, new_params, time = time, easing = easing, loop = loop) + for(var/param in params) + filter_data[name][param] = params[param] + +/atom/proc/change_filter_priority(name, new_priority) + if(!filter_data || !filter_data[name]) + return + + filter_data[name]["priority"] = new_priority + update_filters() + +/obj/item/update_filters() + . = ..() + for(var/X in actions) + var/datum/action/A = X + A.UpdateButtonIcon() /atom/proc/get_filter(name) if(filter_data && filter_data[name]) return filters[filter_data.Find(name)] -/atom/proc/remove_filter(name) - if(filter_data && filter_data[name]) - filter_data -= name - update_filters() - return TRUE +/atom/proc/remove_filter(name_or_names) + if(!filter_data) + return + + var/list/names = islist(name_or_names) ? name_or_names : list(name_or_names) + + for(var/name in names) + if(filter_data[name]) + filter_data -= name + update_filters() + +/atom/proc/clear_filters() + filter_data = null + filters = null /atom/proc/intercept_zImpact(atom/movable/AM, levels = 1) . |= SEND_SIGNAL(src, COMSIG_ATOM_INTERCEPT_Z_FALL, AM, levels) ///Sets the custom materials for an item. -/atom/proc/set_custom_materials(var/list/materials, multiplier = 1) +/atom/proc/set_custom_materials(list/materials, multiplier = 1) if(custom_materials) //Only runs if custom materials existed at first. Should usually be the case but check anyways for(var/i in custom_materials) var/datum/material/custom_material = SSmaterials.GetMaterialRef(i) - custom_material.on_removed(src, material_flags) //Remove the current materials + custom_material.on_removed(src, custom_materials[i], material_flags) //Remove the current materials if(!length(materials)) custom_materials = null diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index a582fa03e4..fbda722e4f 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -331,6 +331,7 @@ return . = anchored anchored = anchorvalue + SEND_SIGNAL(src, COMSIG_OBJ_SETANCHORED, anchorvalue) // SEND_SIGNAL(src, COMSIG_MOVABLE_SET_ANCHORED, anchorvalue) /atom/movable/proc/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) diff --git a/code/game/gamemodes/bloodsucker/bloodsucker.dm b/code/game/gamemodes/bloodsucker/bloodsucker.dm index c54de16e2e..65321f5820 100644 --- a/code/game/gamemodes/bloodsucker/bloodsucker.dm +++ b/code/game/gamemodes/bloodsucker/bloodsucker.dm @@ -24,6 +24,7 @@ traitor_name = "Bloodsucker" antag_flag = ROLE_BLOODSUCKER false_report_weight = 1 + chaos = 4 restricted_jobs = list("AI","Cyborg") protected_jobs = list("Chaplain", "Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster") required_players = 20 @@ -87,7 +88,7 @@ // Init Sunlight (called from datum_bloodsucker.on_gain(), in case game mode isn't even Bloodsucker /datum/game_mode/proc/check_start_sunlight() // Already Sunlight (and not about to cancel) - if(istype(bloodsucker_sunlight) && !bloodsucker_sunlight.cancel_me) + if(istype(bloodsucker_sunlight)) return bloodsucker_sunlight = new () @@ -97,7 +98,6 @@ if(!istype(bloodsucker_sunlight)) return if(bloodsuckers.len <= 0) - bloodsucker_sunlight.cancel_me = TRUE qdel(bloodsucker_sunlight) bloodsucker_sunlight = null diff --git a/code/game/gamemodes/brother/traitor_bro.dm b/code/game/gamemodes/brother/traitor_bro.dm index 718ed2c103..eda6b5f9e2 100644 --- a/code/game/gamemodes/brother/traitor_bro.dm +++ b/code/game/gamemodes/brother/traitor_bro.dm @@ -6,6 +6,7 @@ name = "traitor+brothers" config_tag = "traitorbro" required_players = 25 + chaos = 5 restricted_jobs = list("AI", "Cyborg") protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster") diff --git a/code/game/gamemodes/changeling/changeling.dm b/code/game/gamemodes/changeling/changeling.dm index 8029685bf7..20ef83a54c 100644 --- a/code/game/gamemodes/changeling/changeling.dm +++ b/code/game/gamemodes/changeling/changeling.dm @@ -10,6 +10,7 @@ GLOBAL_VAR(changeling_team_objective_type) //If this is not null, we hand our th config_tag = "changeling" antag_flag = ROLE_CHANGELING false_report_weight = 10 + chaos = 5 restricted_jobs = list("AI", "Cyborg") protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster") //citadel change - adds HoP, CE, CMO, and RD to ling role blacklist required_players = 15 diff --git a/code/game/gamemodes/changeling/traitor_chan.dm b/code/game/gamemodes/changeling/traitor_chan.dm index b010b08bc3..88a1cde8ce 100644 --- a/code/game/gamemodes/changeling/traitor_chan.dm +++ b/code/game/gamemodes/changeling/traitor_chan.dm @@ -2,6 +2,7 @@ name = "traitor+changeling" config_tag = "traitorchan" false_report_weight = 10 + chaos = 6 traitors_possible = 3 //hard limit on traitors if scaling is turned off restricted_jobs = list("AI", "Cyborg") required_players = 25 diff --git a/code/game/gamemodes/clock_cult/clock_cult.dm b/code/game/gamemodes/clock_cult/clock_cult.dm index 29455afe56..d8ebf6f20c 100644 --- a/code/game/gamemodes/clock_cult/clock_cult.dm +++ b/code/game/gamemodes/clock_cult/clock_cult.dm @@ -134,6 +134,7 @@ Credit where due: config_tag = "clockwork_cult" antag_flag = ROLE_SERVANT_OF_RATVAR false_report_weight = 10 + chaos = 8 required_players = 24 //Fixing this directly for now since apparently config machine for forcing modes broke. required_enemies = 3 recommended_enemies = 5 diff --git a/code/game/gamemodes/clown_ops/clown_ops.dm b/code/game/gamemodes/clown_ops/clown_ops.dm index 108c67ad27..659d2de105 100644 --- a/code/game/gamemodes/clown_ops/clown_ops.dm +++ b/code/game/gamemodes/clown_ops/clown_ops.dm @@ -1,7 +1,7 @@ /datum/game_mode/nuclear/clown_ops name = "clown ops" config_tag = "clownops" - + chaos = 8 announce_span = "danger" announce_text = "Clown empire forces are approaching the station in an attempt to HONK it!\n\ Operatives: Secure the nuclear authentication disk and use your bananium fission explosive to HONK the station.\n\ diff --git a/code/game/gamemodes/cult/cult.dm b/code/game/gamemodes/cult/cult.dm index 8ec4123201..ba9fad7a84 100644 --- a/code/game/gamemodes/cult/cult.dm +++ b/code/game/gamemodes/cult/cult.dm @@ -38,6 +38,7 @@ config_tag = "cult" antag_flag = ROLE_CULTIST false_report_weight = 10 + chaos = 8 restricted_jobs = list("AI", "Cyborg") protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster") required_players = 30 diff --git a/code/game/gamemodes/devil/devil agent/devil_agent.dm b/code/game/gamemodes/devil/devil agent/devil_agent.dm index 789cff5c8f..ccfd6f1dd5 100644 --- a/code/game/gamemodes/devil/devil agent/devil_agent.dm +++ b/code/game/gamemodes/devil/devil agent/devil_agent.dm @@ -1,6 +1,7 @@ /datum/game_mode/devil/devil_agents name = "Devil Agents" config_tag = "devil_agents" + chaos = 5 required_players = 25 required_enemies = 3 recommended_enemies = 8 diff --git a/code/game/gamemodes/devil/devil_game_mode.dm b/code/game/gamemodes/devil/devil_game_mode.dm index 0f2e8f7858..9bf7fc0e82 100644 --- a/code/game/gamemodes/devil/devil_game_mode.dm +++ b/code/game/gamemodes/devil/devil_game_mode.dm @@ -3,6 +3,7 @@ config_tag = "devil" antag_flag = ROLE_DEVIL false_report_weight = 1 + chaos = 3 protected_jobs = list("Lawyer", "Curator", "Chaplain", "Head of Security", "Captain", "AI") required_players = 0 required_enemies = 1 diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets.dm b/code/game/gamemodes/dynamic/dynamic_rulesets.dm index 3e7e504130..f1e48eb31c 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets.dm @@ -106,6 +106,7 @@ for(var/i in 1 to 3) if(config_tag in saved_dynamic_rules[i]) weight_mult -= (repeated_mode_adjust[i]/100) + weight_mult = max(0,weight_mult) if(config_tag in costs) cost = costs[config_tag] if(config_tag in requirementses) diff --git a/code/game/gamemodes/eldritch_cult/eldritch_cult.dm b/code/game/gamemodes/eldritch_cult/eldritch_cult.dm index a3e3c54dce..1693163fa2 100644 --- a/code/game/gamemodes/eldritch_cult/eldritch_cult.dm +++ b/code/game/gamemodes/eldritch_cult/eldritch_cult.dm @@ -3,6 +3,7 @@ config_tag = "heresy" antag_flag = ROLE_HERETIC false_report_weight = 5 + chaos = 5 restricted_jobs = list("AI", "Cyborg") protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster") //citadel change - adds HoP, CE, CMO, and RD to heretic role blacklist required_players = 15 diff --git a/code/game/gamemodes/extended/extended.dm b/code/game/gamemodes/extended/extended.dm index 976f3e3eca..a6abcbefbd 100644 --- a/code/game/gamemodes/extended/extended.dm +++ b/code/game/gamemodes/extended/extended.dm @@ -3,6 +3,7 @@ config_tag = "secret_extended" false_report_weight = 5 required_players = 0 + chaos = 0 announce_span = "notice" announce_text = "Just have fun and enjoy the game!" diff --git a/code/game/gamemodes/game_mode.dm b/code/game/gamemodes/game_mode.dm index 3d7eeb7a8a..10c0154412 100644 --- a/code/game/gamemodes/game_mode.dm +++ b/code/game/gamemodes/game_mode.dm @@ -17,6 +17,7 @@ var/config_tag = null var/votable = 1 var/probability = 0 + var/chaos = 5 // 0-9, used for weighting round-to-round var/false_report_weight = 0 //How often will this show up incorrectly in a centcom report? var/station_was_nuked = 0 //see nuclearbomb.dm and malfunction.dm var/nuke_off_station = 0 //Used for tracking where the nuke hit @@ -81,30 +82,43 @@ ///Everyone should now be on the station and have their normal gear. This is the place to give the special roles extra things /datum/game_mode/proc/post_setup(report) //Gamemodes can override the intercept report. Passing TRUE as the argument will force a report. - //finalize_monster_hunters() Disabled for now if(!report) report = !CONFIG_GET(flag/no_intercept_report) addtimer(CALLBACK(GLOBAL_PROC, .proc/display_roundstart_logout_report), ROUNDSTART_LOGOUT_REPORT_TIME) - if(prob(20)) //CIT CHANGE - adds a 20% chance for the security level to be the opposite of what it normally is + if(prob(20)) //cit-change flipseclevel = TRUE + + // if(CONFIG_GET(flag/reopen_roundstart_suicide_roles)) + // var/delay = CONFIG_GET(number/reopen_roundstart_suicide_roles_delay) + // if(delay) + // delay = (delay SECONDS) + // else + // delay = (4 MINUTES) //default to 4 minutes if the delay isn't defined. + // addtimer(CALLBACK(GLOBAL_PROC, .proc/reopen_roundstart_suicide_roles), delay) + if(SSdbcore.Connect()) - var/sql + var/list/to_set = list() + var/arguments = list() if(SSticker.mode) - sql += "game_mode = '[SSticker.mode]'" + to_set += "game_mode = :game_mode" + arguments["game_mode"] = SSticker.mode if(GLOB.revdata.originmastercommit) - if(sql) - sql += ", " - sql += "commit_hash = '[GLOB.revdata.originmastercommit]'" - if(sql) - var/datum/DBQuery/query_round_game_mode = SSdbcore.NewQuery("UPDATE [format_table_name("round")] SET [sql] WHERE id = [GLOB.round_id]") + to_set += "commit_hash = :commit_hash" + arguments["commit_hash"] = GLOB.revdata.originmastercommit + if(to_set.len) + arguments["round_id"] = GLOB.round_id + var/datum/db_query/query_round_game_mode = SSdbcore.NewQuery( + "UPDATE [format_table_name("round")] SET [to_set.Join(", ")] WHERE id = :round_id", + arguments + ) query_round_game_mode.Execute() qdel(query_round_game_mode) if(report) addtimer(CALLBACK(src, .proc/send_intercept, 0), rand(waittime_l, waittime_h)) generate_station_goals() gamemode_ready = TRUE - return 1 + return TRUE ///Handles late-join antag assignments @@ -610,3 +624,10 @@ /// Mode specific info for ghost game_info /datum/game_mode/proc/ghost_info() return + +/datum/game_mode/proc/get_chaos() + var/chaos_levels = CONFIG_GET(keyed_list/chaos_level) + if(config_tag in chaos_levels) + return chaos_levels[config_tag] + else + return chaos diff --git a/code/game/gamemodes/gangs/gangs.dm b/code/game/gamemodes/gangs/gangs.dm index 0dc4a520ef..100669c487 100644 --- a/code/game/gamemodes/gangs/gangs.dm +++ b/code/game/gamemodes/gangs/gangs.dm @@ -6,6 +6,7 @@ GLOBAL_LIST_EMPTY(gangs) name = "gang war" config_tag = "gang" antag_flag = ROLE_GANG + chaos = 9 restricted_jobs = list("AI", "Cyborg") protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster") required_players = 15 diff --git a/code/game/gamemodes/meteor/meteor.dm b/code/game/gamemodes/meteor/meteor.dm index afeebb770b..78fe6d9324 100644 --- a/code/game/gamemodes/meteor/meteor.dm +++ b/code/game/gamemodes/meteor/meteor.dm @@ -2,6 +2,7 @@ name = "meteor" config_tag = "meteor" false_report_weight = 1 + chaos = 9 var/meteordelay = 2000 var/nometeors = 0 var/rampupdelta = 5 diff --git a/code/game/gamemodes/meteor/meteors.dm b/code/game/gamemodes/meteor/meteors.dm index 5cfec2376a..9953b593d9 100644 --- a/code/game/gamemodes/meteor/meteors.dm +++ b/code/game/gamemodes/meteor/meteors.dm @@ -1,4 +1,6 @@ #define DEFAULT_METEOR_LIFETIME 1800 +#define MAP_EDGE_PAD 5 + GLOBAL_VAR_INIT(meteor_wave_delay, 625) //minimum wait between waves in tenths of seconds //set to at least 100 unless you want evarr ruining every round @@ -30,7 +32,7 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust)) //for space dust event var/turf/pickedgoal var/max_i = 10//number of tries to spawn meteor. while(!isspaceturf(pickedstart)) - var/startSide = dir || pick(GLOB.cardinals) + var/startSide = (dir ? dir : pick(GLOB.cardinals)) var/startZ = pick(SSmapping.levels_by_trait(ZTRAIT_STATION)) pickedstart = spaceDebrisStartLoc(startSide, startZ) pickedgoal = spaceDebrisFinishLoc(startSide, startZ) @@ -46,17 +48,17 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust)) //for space dust event var/startx switch(startSide) if(NORTH) - starty = world.maxy-(TRANSITIONEDGE+2) - startx = rand((TRANSITIONEDGE+2), world.maxx-(TRANSITIONEDGE+2)) + starty = world.maxy-(TRANSITIONEDGE + MAP_EDGE_PAD) + startx = rand((TRANSITIONEDGE + MAP_EDGE_PAD), world.maxx-(TRANSITIONEDGE + MAP_EDGE_PAD)) if(EAST) - starty = rand((TRANSITIONEDGE+2),world.maxy-(TRANSITIONEDGE+2)) - startx = world.maxx-(TRANSITIONEDGE+2) + starty = rand((TRANSITIONEDGE + MAP_EDGE_PAD),world.maxy-(TRANSITIONEDGE + MAP_EDGE_PAD)) + startx = world.maxx-(TRANSITIONEDGE + MAP_EDGE_PAD) if(SOUTH) - starty = (TRANSITIONEDGE+2) - startx = rand((TRANSITIONEDGE+2), world.maxx-(TRANSITIONEDGE+2)) + starty = (TRANSITIONEDGE + MAP_EDGE_PAD) + startx = rand((TRANSITIONEDGE + MAP_EDGE_PAD), world.maxx-(TRANSITIONEDGE + MAP_EDGE_PAD)) if(WEST) - starty = rand((TRANSITIONEDGE+2), world.maxy-(TRANSITIONEDGE+2)) - startx = (TRANSITIONEDGE+2) + starty = rand((TRANSITIONEDGE + MAP_EDGE_PAD), world.maxy-(TRANSITIONEDGE + MAP_EDGE_PAD)) + startx = (TRANSITIONEDGE + MAP_EDGE_PAD) . = locate(startx, starty, Z) /proc/spaceDebrisFinishLoc(startSide, Z) @@ -64,17 +66,17 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust)) //for space dust event var/endx switch(startSide) if(NORTH) - endy = (TRANSITIONEDGE+1) - endx = rand((TRANSITIONEDGE+1), world.maxx-(TRANSITIONEDGE+1)) + endy = (TRANSITIONEDGE + MAP_EDGE_PAD) + endx = rand((TRANSITIONEDGE + MAP_EDGE_PAD), world.maxx-(TRANSITIONEDGE + MAP_EDGE_PAD)) if(EAST) - endy = rand((TRANSITIONEDGE+1), world.maxy-(TRANSITIONEDGE+1)) - endx = (TRANSITIONEDGE+1) + endy = rand((TRANSITIONEDGE + MAP_EDGE_PAD), world.maxy-(TRANSITIONEDGE + MAP_EDGE_PAD)) + endx = (TRANSITIONEDGE + MAP_EDGE_PAD) if(SOUTH) - endy = world.maxy-(TRANSITIONEDGE+1) - endx = rand((TRANSITIONEDGE+1), world.maxx-(TRANSITIONEDGE+1)) + endy = world.maxy-(TRANSITIONEDGE + MAP_EDGE_PAD) + endx = rand((TRANSITIONEDGE + MAP_EDGE_PAD), world.maxx-(TRANSITIONEDGE + MAP_EDGE_PAD)) if(WEST) - endy = rand((TRANSITIONEDGE+1),world.maxy-(TRANSITIONEDGE+1)) - endx = world.maxx-(TRANSITIONEDGE+1) + endy = rand((TRANSITIONEDGE + MAP_EDGE_PAD),world.maxy-(TRANSITIONEDGE + MAP_EDGE_PAD)) + endx = world.maxx-(TRANSITIONEDGE + MAP_EDGE_PAD) . = locate(endx, endy, Z) /////////////////////// @@ -82,7 +84,7 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust)) //for space dust event ////////////////////// /obj/effect/meteor - name = "the concept of meteor" + name = "\proper the concept of meteor" desc = "You should probably run instead of gawking at this." icon = 'icons/obj/meteor.dmi' icon_state = "small" @@ -92,7 +94,7 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust)) //for space dust event var/hitpwr = 2 //Level of ex_act to be called on hit. var/dest pass_flags = PASSTABLE - var/heavy = 0 + var/heavy = FALSE var/meteorsound = 'sound/effects/meteorimpact.ogg' var/z_original var/threat = 0 // used for determining which meteors are most interesting @@ -108,12 +110,12 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust)) //for space dust event . = ..() //process movement... + var/turf/T = get_turf(loc) if(.)//.. if did move, ram the turf we get in - var/turf/T = get_turf(loc) ram_turf(T) - if(prob(10) && !isspaceturf(T) && !istype(T, /turf/closed/mineral) && !istype(T, /turf/open/floor/plating/asteroid))//randomly takes a 'hit' from ramming - get_hit() + if(prob(10) && !isspaceturf(T) && !istype(T, /turf/closed/mineral) && !istype(T, /turf/open/floor/plating/asteroid))//randomly takes a 'hit' from ramming, and ignore spare ruin aseroids + get_hit() /obj/effect/meteor/Destroy() if (timerid) @@ -135,24 +137,25 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust)) //for space dust event /obj/effect/meteor/Bump(atom/A) if(A) ram_turf(get_turf(A)) - playsound(src.loc, meteorsound, 40, 1) - if(!istype(A, /turf/closed/mineral) && !istype(A, /turf/open/floor/plating/asteroid)) + playsound(src.loc, meteorsound, 40, TRUE) + if(!istype(A, /turf/closed/mineral) && !istype(A, /turf/open/floor/plating/asteroid)) // ignore localstation ruins get_hit() /obj/effect/meteor/proc/ram_turf(turf/T) //first bust whatever is in the turf - for(var/atom/A in T) - if(A != src) - if(isliving(A)) - A.visible_message("[src] slams into [A].", "[src] slams into you!.") - A.ex_act(hitpwr) + for(var/thing in T) + if(thing == src) + continue + if(isliving(thing)) + var/mob/living/living_thing = thing + living_thing.visible_message("[src] slams into [living_thing].", "[src] slams into you!.") + living_thing.ex_act(hitpwr) //then, ram the turf if it still exists if(T) T.ex_act(hitpwr) - //process getting 'hit' by colliding with a dense object //or randomly when ramming turfs /obj/effect/meteor/proc/get_hit() @@ -162,13 +165,10 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust)) //for space dust event meteor_effect() qdel(src) -/obj/effect/meteor/ex_act() - return - /obj/effect/meteor/examine(mob/user) + . = ..() if(!(flags_1 & ADMIN_SPAWNED_1) && isliving(user)) - SSmedals.UnlockMedal(MEDAL_METEOR, user.client) - return ..() + user.client.give_award(/datum/award/achievement/misc/meteor_examine, user) /obj/effect/meteor/attackby(obj/item/I, mob/user, params) if(I.tool_behaviour == TOOL_MINING) @@ -232,7 +232,7 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust)) //for space dust event name = "big meteor" icon_state = "large" hits = 6 - heavy = 1 + heavy = TRUE dropamt = 4 threat = 10 @@ -245,7 +245,7 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust)) //for space dust event name = "flaming meteor" icon_state = "flaming" hits = 5 - heavy = 1 + heavy = TRUE meteorsound = 'sound/effects/bamf.ogg' meteordrop = list(/obj/item/stack/ore/plasma) threat = 20 @@ -258,7 +258,7 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust)) //for space dust event /obj/effect/meteor/irradiated name = "glowing meteor" icon_state = "glowing" - heavy = 1 + heavy = TRUE meteordrop = list(/obj/item/stack/ore/uranium) threat = 15 @@ -275,7 +275,7 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust)) //for space dust event icon_state = "meateor" desc = "Just... don't think too hard about where this thing came from." hits = 2 - heavy = 1 + heavy = TRUE meteorsound = 'sound/effects/blobattack.ogg' meteordrop = list(/obj/item/reagent_containers/food/snacks/meat/slab/human, /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant, /obj/item/organ/heart, /obj/item/organ/lungs, /obj/item/organ/tongue, /obj/item/organ/appendix/) var/meteorgibs = /obj/effect/gibspawner/generic @@ -327,7 +327,7 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust)) //for space dust event desc = "Your life briefly passes before your eyes the moment you lay them on this monstrosity." hits = 30 hitpwr = 1 - heavy = 1 + heavy = TRUE meteorsound = 'sound/effects/bamf.ogg' meteordrop = list(/obj/item/stack/ore/plasma) threat = 50 @@ -358,7 +358,7 @@ GLOBAL_LIST_INIT(meteorsSPOOKY, list(/obj/effect/meteor/pumpkin)) icon = 'icons/obj/meteor_spooky.dmi' icon_state = "pumpkin" hits = 10 - heavy = 1 + heavy = TRUE dropamt = 1 meteordrop = list(/obj/item/clothing/head/hardhat/pumpkinhead, /obj/item/reagent_containers/food/snacks/grown/pumpkin) threat = 100 @@ -368,3 +368,4 @@ GLOBAL_LIST_INIT(meteorsSPOOKY, list(/obj/effect/meteor/pumpkin)) meteorsound = pick('sound/hallucinations/im_here1.ogg','sound/hallucinations/im_here2.ogg') ////////////////////////// #undef DEFAULT_METEOR_LIFETIME +#undef MAP_EDGE_PAD diff --git a/code/game/gamemodes/monkey/monkey.dm b/code/game/gamemodes/monkey/monkey.dm index 76460ffbb8..c91252258a 100644 --- a/code/game/gamemodes/monkey/monkey.dm +++ b/code/game/gamemodes/monkey/monkey.dm @@ -11,6 +11,7 @@ required_players = 20 required_enemies = 1 recommended_enemies = 1 + chaos = 9 restricted_jobs = list("Cyborg", "AI") diff --git a/code/game/gamemodes/nuclear/nuclear.dm b/code/game/gamemodes/nuclear/nuclear.dm index 48a298984c..dcf84e84db 100644 --- a/code/game/gamemodes/nuclear/nuclear.dm +++ b/code/game/gamemodes/nuclear/nuclear.dm @@ -2,6 +2,7 @@ name = "nuclear emergency" config_tag = "nuclear" false_report_weight = 10 + chaos = 9 required_players = 28 // 30 players - 3 players to be the nuke ops = 25 players remaining required_enemies = 2 recommended_enemies = 5 diff --git a/code/game/gamemodes/overthrow/overthrow.dm b/code/game/gamemodes/overthrow/overthrow.dm index dca0c1ade1..6a118567df 100644 --- a/code/game/gamemodes/overthrow/overthrow.dm +++ b/code/game/gamemodes/overthrow/overthrow.dm @@ -3,6 +3,7 @@ name = "overthrow" config_tag = "overthrow" antag_flag = ROLE_OVERTHROW + chaos = 5 restricted_jobs = list("AI", "Cyborg") protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster") required_players = 20 // the core idea is of a swift, bloodless coup, so it shouldn't be as chaotic as revs. diff --git a/code/game/gamemodes/revolution/revolution.dm b/code/game/gamemodes/revolution/revolution.dm index 9eed18d906..419a74d616 100644 --- a/code/game/gamemodes/revolution/revolution.dm +++ b/code/game/gamemodes/revolution/revolution.dm @@ -12,6 +12,7 @@ config_tag = "revolution" antag_flag = ROLE_REV false_report_weight = 10 + chaos = 8 restricted_jobs = list("AI", "Cyborg") protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster") required_players = 20 diff --git a/code/game/gamemodes/traitor/double_agents.dm b/code/game/gamemodes/traitor/double_agents.dm index fc669d4855..c64e508cef 100644 --- a/code/game/gamemodes/traitor/double_agents.dm +++ b/code/game/gamemodes/traitor/double_agents.dm @@ -10,6 +10,7 @@ required_enemies = 5 recommended_enemies = 8 reroll_friendly = 0 + chaos = 7 traitor_name = "Nanotrasen Internal Affairs Agent" antag_flag = ROLE_INTERNAL_AFFAIRS diff --git a/code/game/gamemodes/traitor/traitor.dm b/code/game/gamemodes/traitor/traitor.dm index 8b1b18660a..d42fe615cd 100644 --- a/code/game/gamemodes/traitor/traitor.dm +++ b/code/game/gamemodes/traitor/traitor.dm @@ -17,6 +17,7 @@ recommended_enemies = 4 reroll_friendly = 1 enemy_minimum_age = 0 + chaos = 2 announce_span = "danger" announce_text = "There are Syndicate agents on the station!\n\ diff --git a/code/game/gamemodes/wizard/wizard.dm b/code/game/gamemodes/wizard/wizard.dm index 23f065318e..d8cb851aad 100644 --- a/code/game/gamemodes/wizard/wizard.dm +++ b/code/game/gamemodes/wizard/wizard.dm @@ -12,6 +12,7 @@ recommended_enemies = 1 enemy_minimum_age = 7 round_ends_with_antag_death = 1 + chaos = 9 announce_span = "danger" announce_text = "There is a space wizard attacking the station!\n\ Wizard: Accomplish your objectives and cause mayhem on the station.\n\ diff --git a/code/game/machinery/_machinery.dm b/code/game/machinery/_machinery.dm index 8d7a8c047a..e37fd13106 100644 --- a/code/game/machinery/_machinery.dm +++ b/code/game/machinery/_machinery.dm @@ -137,7 +137,7 @@ Class Procs: GLOB.machines += src if(ispath(circuit, /obj/item/circuitboard)) - circuit = new circuit + circuit = new circuit(src) circuit.apply_default_parts(src) if(!speed_process && init_process) @@ -361,11 +361,11 @@ Class Procs: /obj/machinery/deconstruct(disassembled = TRUE) if(!(flags_1 & NODECONSTRUCT_1)) on_deconstruction() - if(component_parts && component_parts.len) + if(LAZYLEN(component_parts)) spawn_frame(disassembled) for(var/obj/item/I in component_parts) I.forceMove(loc) - component_parts.Cut() + LAZYCLEARLIST(component_parts) qdel(src) /obj/machinery/proc/spawn_frame(disassembled) @@ -539,12 +539,17 @@ Class Procs: /obj/machinery/Exited(atom/movable/AM, atom/newloc) . = ..() + // if(AM == occupant) + // set_occupant(null) if (AM == occupant) SEND_SIGNAL(src, COMSIG_MACHINE_EJECT_OCCUPANT, occupant) occupant = null + if(AM == circuit && circuit.loc != src) + component_parts -= AM //TODO: make the cmp part functions use lazyX + circuit = null -/obj/machinery/proc/adjust_item_drop_location(atom/movable/AM) // Adjust item drop location to a 3x3 grid inside the tile, returns slot id from 0 to 8 - var/md5 = md5(AM.name) // Oh, and it's deterministic too. A specific item will always drop from the same slot. +/obj/machinery/proc/adjust_item_drop_location(atom/movable/AM) // Adjust item drop location to a 3x3 grid inside the tile, returns slot id from 0 to 8 + var/md5 = md5(AM.name) // Oh, and it's deterministic too. A specific item will always drop from the same slot. for (var/i in 1 to 32) . += hex2num(md5[i]) . = . % 9 diff --git a/code/game/machinery/aug_manipulator.dm b/code/game/machinery/aug_manipulator.dm index 6fdeae61a1..e496973d75 100644 --- a/code/game/machinery/aug_manipulator.dm +++ b/code/game/machinery/aug_manipulator.dm @@ -8,7 +8,19 @@ max_integrity = 200 var/obj/item/bodypart/storedpart var/initial_icon_state - var/static/list/style_list_icons = list("standard" = 'icons/mob/augmentation/augments.dmi', "engineer" = 'icons/mob/augmentation/augments_engineer.dmi', "security" = 'icons/mob/augmentation/augments_security.dmi', "mining" = 'icons/mob/augmentation/augments_mining.dmi') + var/static/list/style_list_icons = list("standard" = 'icons/mob/augmentation/augments.dmi', + "engineer" = 'icons/mob/augmentation/augments_engineer.dmi', + "security" = 'icons/mob/augmentation/augments_security.dmi', + "mining" = 'icons/mob/augmentation/augments_mining.dmi', + "Talon" = 'icons/mob/augmentation/cosmetic_prosthetic/talon.dmi', + "Nanotrasen" = 'icons/mob/augmentation/cosmetic_prosthetic/nanotrasen.dmi', + "Hephaesthus" = 'icons/mob/augmentation/cosmetic_prosthetic/hephaestus.dmi', + "Bishop" = 'icons/mob/augmentation/cosmetic_prosthetic/bishop.dmi', + "Xion" = 'icons/mob/augmentation/cosmetic_prosthetic/xion.dmi', + "Grayson" = 'icons/mob/augmentation/cosmetic_prosthetic/grayson.dmi', + "Cybersolutions" = 'icons/mob/augmentation/cosmetic_prosthetic/cybersolutions.dmi', + "Ward" = 'icons/mob/augmentation/cosmetic_prosthetic/ward.dmi' + ) /obj/machinery/aug_manipulator/examine(mob/user) . = ..() diff --git a/code/game/machinery/autolathe.dm b/code/game/machinery/autolathe.dm index 1f0687151d..d8a5f7d2c7 100644 --- a/code/game/machinery/autolathe.dm +++ b/code/game/machinery/autolathe.dm @@ -1,6 +1,6 @@ -#define AUTOLATHE_MAIN_MENU 1 -#define AUTOLATHE_CATEGORY_MENU 2 -#define AUTOLATHE_SEARCH_MENU 3 +#define AUTOLATHE_MAIN_MENU 1 +#define AUTOLATHE_CATEGORY_MENU 2 +#define AUTOLATHE_SEARCH_MENU 3 /obj/machinery/autolathe name = "autolathe" @@ -17,7 +17,7 @@ var/list/L = list() var/list/LL = list() var/hacked = FALSE - var/disabled = 0 + var/disabled = FALSE var/shocked = FALSE var/hack_wire var/disable_wire @@ -27,13 +27,13 @@ var/prod_coeff = 1 var/datum/design/being_built + var/datum/techweb/stored_research var/list/datum/design/matching_designs var/selected_category var/screen = 1 var/base_price = 25 var/hacked_price = 50 - var/datum/techweb/specialized/autounlocking/stored_research = /datum/techweb/specialized/autounlocking/autolathe var/list/categories = list( "Tools", "Electronics", @@ -46,19 +46,13 @@ "Dinnerware", "Imported" ) - var/list/allowed_materials - - /// Base print speed - var/base_print_speed = 10 /obj/machinery/autolathe/Initialize() - var/list/mats = allowed_materials - if(!mats) - mats = SSmaterials.materialtypes_by_category[MAT_CATEGORY_RIGID] - AddComponent(/datum/component/material_container, mats, _show_on_examine=TRUE, _after_insert=CALLBACK(src, .proc/AfterMaterialInsert)) + AddComponent(/datum/component/material_container, SSmaterials.materialtypes_by_category[MAT_CATEGORY_RIGID], 0, TRUE, null, null, CALLBACK(src, .proc/AfterMaterialInsert)) . = ..() + wires = new /datum/wires/autolathe(src) - stored_research = new stored_research + stored_research = new /datum/techweb/specialized/autounlocking/autolathe matching_designs = list() /obj/machinery/autolathe/Destroy() @@ -83,7 +77,7 @@ if(AUTOLATHE_SEARCH_MENU) dat = search_win(user) - var/datum/browser/popup = new(user, name, name, 400, 500) + var/datum/browser/popup = new(user, "autolathe", name, 400, 500) popup.set_content(dat) popup.open() @@ -114,9 +108,9 @@ return TRUE if(istype(O, /obj/item/disk/design_disk)) - user.visible_message("[user] begins to load \the [O] in \the [src]...", - "You begin to load a design from \the [O]...", - "You hear the chatter of a floppy drive.") + user.visible_message("[user] begins to load \the [O] in \the [src]...", + "You begin to load a design from \the [O]...", + "You hear the chatter of a floppy drive.") busy = TRUE var/obj/item/disk/design_disk/D = O if(do_after(user, 14.4, target = src)) @@ -128,14 +122,16 @@ return ..() -/obj/machinery/autolathe/proc/AfterMaterialInsert(obj/item/item_inserted, id_inserted, amount_inserted) + +/obj/machinery/autolathe/proc/AfterMaterialInsert(item_inserted, id_inserted, amount_inserted) if(istype(item_inserted, /obj/item/stack/ore/bluespace_crystal)) use_power(MINERAL_MATERIAL_AMOUNT / 10) - else if(item_inserted.custom_materials?.len && item_inserted.custom_materials[SSmaterials.GetMaterialRef(/datum/material/glass)]) + else if(custom_materials && custom_materials.len && custom_materials[SSmaterials.GetMaterialRef(/datum/material/glass)]) flick("autolathe_r",src)//plays glass insertion animation by default otherwise else flick("autolathe_o",src)//plays metal insertion animation + use_power(min(1000, amount_inserted / 100)) updateUsrDialog() @@ -187,7 +183,7 @@ if(materials.materials[i] > 0) list_to_show += i - used_material = input("Choose [used_material]", "Custom Material") as null|anything in list_to_show + used_material = input("Choose [used_material]", "Custom Material") as null|anything in sortList(list_to_show, /proc/cmp_typepaths_asc) if(!used_material) return //Didn't pick any material, so you can't build shit either. custom_materials[used_material] += amount_needed @@ -198,8 +194,8 @@ busy = TRUE use_power(power) icon_state = "autolathe_n" - var/time = is_stack ? 10 : base_print_speed * coeff * multiplier - addtimer(CALLBACK(src, .proc/make_item, power, materials_used, custom_materials, multiplier, coeff, is_stack), time) + var/time = is_stack ? 32 : (32 * coeff * multiplier) ** 0.8 + addtimer(CALLBACK(src, .proc/make_item, power, materials_used, custom_materials, multiplier, coeff, is_stack, usr), time) else to_chat(usr, "Not enough materials for this operation.") @@ -218,10 +214,11 @@ return -/obj/machinery/autolathe/proc/make_item(power, var/list/materials_used, var/list/picked_materials, multiplier, coeff, is_stack) +/obj/machinery/autolathe/proc/make_item(power, list/materials_used, list/picked_materials, multiplier, coeff, is_stack, mob/user) var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) var/atom/A = drop_location() use_power(power) + materials.use_materials(materials_used) if(is_stack) @@ -235,6 +232,11 @@ if(length(picked_materials)) new_item.set_custom_materials(picked_materials, 1 / multiplier) //Ensure we get the non multiplied amount + for(var/x in picked_materials) + var/datum/material/M = x + if(!istype(M, /datum/material/glass) && !istype(M, /datum/material/iron)) + user.client.give_award(/datum/award/achievement/misc/getting_an_upgrade, user) + icon_state = "autolathe" busy = FALSE @@ -246,12 +248,10 @@ T += MB.rating*75000 var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) materials.max_amount = T - var/manips = 0 - var/total_manip_rating = 0 + T=1.2 for(var/obj/item/stock_parts/manipulator/M in component_parts) - total_manip_rating += M.rating - manips++ - prod_coeff = STANDARD_PART_LEVEL_LATHE_COEFFICIENT(total_manip_rating / (manips? manips : 1)) + T -= M.rating*0.2 + prod_coeff = min(1,max(0,T)) // Coeff going 1 -> 0,8 -> 0,6 -> 0,4 /obj/machinery/autolathe/examine(mob/user) . += ..() @@ -376,6 +376,7 @@ return materials.has_materials(required_materials) + /obj/machinery/autolathe/proc/get_design_cost(datum/design/D) var/coeff = (ispath(D.build_path, /obj/item/stack) ? 1 : prod_coeff) var/dat @@ -416,9 +417,7 @@ hacked = state for(var/id in SSresearch.techweb_designs) var/datum/design/D = SSresearch.techweb_design_by_id(id) - if(D.build_type & stored_research.design_autounlock_skip_types) - continue - if((D.build_type & stored_research.design_autounlock_buildtypes) && ("hacked" in D.category)) + if((D.build_type & AUTOLATHE) && ("hacked" in D.category)) if(hacked) stored_research.add_design(D) else @@ -428,19 +427,24 @@ . = ..() adjust_hacked(TRUE) +//Called when the object is constructed by an autolathe +//Has a reference to the autolathe so you can do !!FUN!! things with hacked lathes +/obj/item/proc/autolathe_crafted(obj/machinery/autolathe/A) + return + /obj/machinery/autolathe/secure name = "secured autolathe" desc = "It produces items using metal and glass. This model was reprogrammed without some of the more hazardous designs." circuit = /obj/item/circuitboard/machine/autolathe/secure - stored_research = /datum/techweb/specialized/autounlocking/autolathe/public - base_print_speed = 20 + +/obj/machinery/autolathe/secure/Initialize() + . = ..() + stored_research = new /datum/techweb/specialized/autounlocking/autolathe/public /obj/machinery/autolathe/toy name = "autoylathe" desc = "It produces toys using plastic, metal and glass." circuit = /obj/item/circuitboard/machine/autolathe/toy - - stored_research = /datum/techweb/specialized/autounlocking/autolathe/toy categories = list( "Toys", "Figurines", @@ -453,12 +457,8 @@ "Misc", "Imported" ) - allowed_materials = list( - /datum/material/iron, - /datum/material/glass, - /datum/material/plastic - ) /obj/machinery/autolathe/toy/hacked/Initialize() . = ..() adjust_hacked(TRUE) + stored_research = new /datum/techweb/specialized/autounlocking/autolathe/toy diff --git a/code/game/machinery/colormate.dm b/code/game/machinery/colormate.dm index 1f1c16248c..d059d492ae 100644 --- a/code/game/machinery/colormate.dm +++ b/code/game/machinery/colormate.dm @@ -44,7 +44,8 @@ icon_state = "colormate" /obj/machinery/gear_painter/Destroy() - inserted.forceMove(drop_location()) + if(inserted) //please i beg you do not drop nulls + inserted.forceMove(drop_location()) return ..() /obj/machinery/gear_painter/attackby(obj/item/I, mob/living/user) diff --git a/code/game/machinery/computer/_computer.dm b/code/game/machinery/computer/_computer.dm index 16081db2c1..0c628ba43e 100644 --- a/code/game/machinery/computer/_computer.dm +++ b/code/game/machinery/computer/_computer.dm @@ -17,20 +17,16 @@ /obj/machinery/computer/Initialize(mapload, obj/item/circuitboard/C) . = ..() + power_change() - if(!QDELETED(C)) - qdel(circuit) - circuit = C - C.moveToNullspace() /obj/machinery/computer/Destroy() - QDEL_NULL(circuit) - return ..() + . = ..() /obj/machinery/computer/process() if(stat & (NOPOWER|BROKEN)) - return 0 - return 1 + return FALSE + return TRUE /obj/machinery/computer/ratvar_act() if(!clockwork) @@ -87,25 +83,32 @@ switch(damage_type) if(BRUTE) if(stat & BROKEN) - playsound(src.loc, 'sound/effects/hit_on_shattered_glass.ogg', 70, 1) + playsound(src.loc, 'sound/effects/hit_on_shattered_glass.ogg', 70, TRUE) else - playsound(src.loc, 'sound/effects/glasshit.ogg', 75, 1) + playsound(src.loc, 'sound/effects/glasshit.ogg', 75, TRUE) if(BURN) - playsound(src.loc, 'sound/items/welder.ogg', 100, 1) + playsound(src.loc, 'sound/items/welder.ogg', 100, TRUE) /obj/machinery/computer/obj_break(damage_flag) - if(circuit && !(flags_1 & NODECONSTRUCT_1)) //no circuit, no breaking - if(!(stat & BROKEN)) - playsound(loc, 'sound/effects/glassbr3.ogg', 100, 1) - stat |= BROKEN - update_icon() - set_light(0) + if(!circuit) //no circuit, no breaking + return + . = ..() + if(. && !(stat & BROKEN)) + stat |= BROKEN + playsound(loc, 'sound/effects/glassbr3.ogg', 100, TRUE) + set_light(0) + update_icon() /obj/machinery/computer/emp_act(severity) . = ..() if (!(. & EMP_PROTECT_SELF)) - if(prob(severity/1.8)) - obj_break("energy") + switch(severity) + if(1) + if(prob(50)) + obj_break("energy") + if(2) + if(prob(10)) + obj_break("energy") /obj/machinery/computer/deconstruct(disassembled = TRUE, mob/user) on_deconstruction() @@ -114,12 +117,14 @@ var/obj/structure/frame/computer/A = new /obj/structure/frame/computer(src.loc) A.setDir(dir) A.circuit = circuit - A.setAnchored(TRUE) + // Circuit removal code is handled in /obj/machinery/Exited() + circuit.forceMove(A) + A.set_anchored(TRUE) if(stat & BROKEN) if(user) to_chat(user, "The broken glass falls out.") else - playsound(src, 'sound/effects/hit_on_shattered_glass.ogg', 70, 1) + playsound(src, 'sound/effects/hit_on_shattered_glass.ogg', 70, TRUE) new /obj/item/shard(drop_location()) new /obj/item/shard(drop_location()) A.state = 3 @@ -129,8 +134,11 @@ to_chat(user, "You disconnect the monitor.") A.state = 4 A.icon_state = "4" - circuit = null for(var/obj/C in src) C.forceMove(loc) - qdel(src) + +/obj/machinery/computer/AltClick(mob/user) + . = ..() + if(!user.canUseTopic(src, !issilicon(user)) || !(stat & (NOPOWER|BROKEN))) + return diff --git a/code/game/machinery/computer/arcade.dm b/code/game/machinery/computer/arcade.dm index 077571b931..dd2ebf287f 100644 --- a/code/game/machinery/computer/arcade.dm +++ b/code/game/machinery/computer/arcade.dm @@ -3,14 +3,7 @@ #define ARCADE_WEIGHT_RARE 1 #define ARCADE_RATIO_PLUSH 0.20 // average 1 out of 6 wins is a plush. -/obj/machinery/computer/arcade - name = "random arcade" - desc = "random arcade machine" - icon_state = "arcade" - icon_keyboard = null - icon_screen = "invaders" - clockwork = TRUE //it'd look weird - var/list/prizes = list( +GLOBAL_LIST_INIT(arcade_prize_pool, list( /obj/item/toy/balloon = ARCADE_WEIGHT_USELESS, /obj/item/toy/beach_ball = ARCADE_WEIGHT_USELESS, /obj/item/toy/cattoy = ARCADE_WEIGHT_USELESS, @@ -70,9 +63,16 @@ /obj/item/clothing/mask/fakemoustache/italian = ARCADE_WEIGHT_RARE, /obj/item/clothing/suit/hooded/wintercoat/ratvar/fake = ARCADE_WEIGHT_TRICK, /obj/item/clothing/suit/hooded/wintercoat/narsie/fake = ARCADE_WEIGHT_TRICK - ) +)) +/obj/machinery/computer/arcade + name = "random arcade" + desc = "random arcade machine" + icon_state = "arcade" + icon_keyboard = "no_keyboard" + icon_screen = "invaders" light_color = LIGHT_COLOR_GREEN + var/list/prize_override /obj/machinery/computer/arcade/proc/Reset() return @@ -91,44 +91,54 @@ var/obj/machinery/computer/arcade/A = new CB.build_path(loc, CB) A.setDir(dir) return INITIALIZE_HINT_QDEL - //The below object acts as a spawner with a wide array of possible picks, most being uninspired references to past/current player characters. - //Nevertheless, this keeps its ratio constant with the sum of all the others prizes. - prizes[/obj/item/toy/plush/random] = counterlist_sum(prizes) * ARCADE_RATIO_PLUSH + Reset() -/obj/machinery/computer/arcade/proc/prizevend(mob/user, list/rarity_classes) - SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "arcade", /datum/mood_event/arcade) +/obj/machinery/computer/arcade/proc/prizevend(mob/user, prizes = 1) + // if(user.mind?.get_skill_level(/datum/skill/gaming) >= SKILL_LEVEL_LEGENDARY && HAS_TRAIT(user, TRAIT_GAMERGOD)) + // visible_message("[user] inputs an intense cheat code!", + // "You hear a flurry of buttons being pressed.") + // say("CODE ACTIVATED: EXTRA PRIZES.") + // prizes *= 2 + for(var/i = 0, i < prizes, i++) + SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "arcade", /datum/mood_event/arcade) + if(prob(0.0001)) //1 in a million + new /obj/item/gun/energy/pulse/prize(src) + visible_message("[src] dispenses.. woah, a gun! Way past cool.", "You hear a chime and a shot.") + user.client.give_award(/datum/award/achievement/misc/pulse, user) + return - if(prob(1) && prob(1) && prob(1)) //Proper 1 in a million - new /obj/item/gun/energy/pulse/prize(src) - SSmedals.UnlockMedal(MEDAL_PULSE, usr.client) + var/prizeselect + if(prize_override) + prizeselect = pickweight(prize_override) + else + prizeselect = pickweight(GLOB.arcade_prize_pool) + var/atom/movable/the_prize = new prizeselect(get_turf(src)) + playsound(src, 'sound/machines/machine_vend.ogg', 50, TRUE, extrarange = -3) + visible_message("[src] dispenses [the_prize]!", "You hear a chime and a clunk.") - if(!contents.len) - var/list/toy_raffle - if(rarity_classes) - for(var/A in prizes) - if(prizes[A] in rarity_classes) - LAZYSET(toy_raffle, A, prizes[A]) - if(!toy_raffle) - toy_raffle = prizes - var/prizeselect = pickweight(toy_raffle) - new prizeselect(src) - - var/atom/movable/prize = pick(contents) - visible_message("[src] dispenses [prize]!", "You hear a chime and a clunk.") - - prize.forceMove(get_turf(src)) /obj/machinery/computer/arcade/emp_act(severity) . = ..() + var/override = FALSE + if(prize_override) + override = TRUE if(stat & (NOPOWER|BROKEN) || . & EMP_PROTECT_SELF) return var/empprize = null - var/num_of_prizes = rand(round(severity/50),round(severity/100)) + var/num_of_prizes = 0 + switch(severity) + if(1) + num_of_prizes = rand(1,4) + if(2) + num_of_prizes = rand(0,2) for(var/i = num_of_prizes; i > 0; i--) - empprize = pickweight(prizes) + if(override) + empprize = pickweight(prize_override) + else + empprize = pickweight(GLOB.arcade_prize_pool) new empprize(loc) explosion(loc, -1, 0, 1+num_of_prizes, flame_range = 1+num_of_prizes) diff --git a/code/game/machinery/computer/arcade/battle.dm b/code/game/machinery/computer/arcade/battle.dm index 96f224f4cf..8240a22290 100644 --- a/code/game/machinery/computer/arcade/battle.dm +++ b/code/game/machinery/computer/arcade/battle.dm @@ -1,130 +1,399 @@ // ** BATTLE ** // - - /obj/machinery/computer/arcade/battle name = "arcade machine" desc = "Does not support Pinball." icon_state = "arcade" circuit = /obj/item/circuitboard/computer/arcade/battle - var/enemy_name = "Space Villain" - var/temp = "Winners don't use space drugs" //Temporary message, for attack messages, etc - var/player_hp = 30 //Player health/attack points - var/player_mp = 10 - var/enemy_hp = 45 //Enemy health/attack points - var/enemy_mp = 20 - var/gameover = FALSE - var/blocked = FALSE //Player cannot attack/heal while set - var/turtle = 0 - var/turn_speed = 5 //Measured in deciseconds. + var/enemy_name = "Space Villain" + ///Enemy health/attack points + var/enemy_hp = 100 + var/enemy_mp = 40 + ///Temporary message, for attack messages, etc + var/temp = "

Winners don't use space drugs

" + ///the list of passive skill the enemy currently has. the actual passives are added in the enemy_setup() proc + var/list/enemy_passive + ///if all the enemy's weakpoints have been triggered becomes TRUE + var/finishing_move = FALSE + ///linked to passives, when it's equal or above the max_passive finishing move will become TRUE + var/pissed_off = 0 + ///the number of passives the enemy will start with + var/max_passive = 3 + ///weapon wielded by the enemy, the shotgun doesn't count. + var/chosen_weapon + + ///Player health + var/player_hp = 85 + ///player magic points + var/player_mp = 20 + ///used to remember the last three move of the player before this turn. + var/list/last_three_move + ///if the enemy or player died. restart the game when TRUE + var/gameover = FALSE + ///the player cannot make any move while this is set to TRUE. should only TRUE during enemy turns. + var/blocked = FALSE + ///used to clear the enemy_action proc timer when the game is restarted + var/timer_id + ///weapon used by the enemy, pure fluff.for certain actions + var/list/weapons + ///unique to the emag mode, acts as a time limit where the player dies when it reaches 0. + var/bomb_cooldown = 19 + +///creates the enemy base stats for a new round along with the enemy passives +/obj/machinery/computer/arcade/battle/proc/enemy_setup(player_skill) + player_hp = 85 + player_mp = 20 + enemy_hp = 100 + enemy_mp = 40 + gameover = FALSE + blocked = FALSE + finishing_move = FALSE + pissed_off = 0 + last_three_move = null + + enemy_passive = list("short_temper" = TRUE, "poisonous" = TRUE, "smart" = TRUE, "shotgun" = TRUE, "magical" = TRUE, "chonker" = TRUE) + for(var/i = LAZYLEN(enemy_passive); i > max_passive; i--) //we'll remove passives from the list until we have the number of passive we want + var/picked_passive = pick(enemy_passive) + LAZYREMOVE(enemy_passive, picked_passive) + + if(LAZYACCESS(enemy_passive, "chonker")) + enemy_hp += 20 + + if(LAZYACCESS(enemy_passive, "shotgun")) + chosen_weapon = "shotgun" + else if(weapons) + chosen_weapon = pick(weapons) + else + chosen_weapon = "null gun" //if the weapons list is somehow empty, shouldn't happen but runtimes are sneaky bastards. + + if(player_skill) + player_hp += player_skill * 2 /obj/machinery/computer/arcade/battle/Reset() + max_passive = 3 var/name_action var/name_part1 var/name_part2 - name_action = pick("Defeat ", "Annihilate ", "Save ", "Strike ", "Stop ", "Destroy ", "Robust ", "Romance ", "Pwn ", "Own ", "Ban ") + if(SSevents.holidays && SSevents.holidays[HALLOWEEN]) + name_action = pick_list(ARCADE_FILE, "rpg_action_halloween") + name_part1 = pick_list(ARCADE_FILE, "rpg_adjective_halloween") + name_part2 = pick_list(ARCADE_FILE, "rpg_enemy_halloween") + weapons = strings(ARCADE_FILE, "rpg_weapon_halloween") + else if(SSevents.holidays && SSevents.holidays[CHRISTMAS]) + name_action = pick_list(ARCADE_FILE, "rpg_action_xmas") + name_part1 = pick_list(ARCADE_FILE, "rpg_adjective_xmas") + name_part2 = pick_list(ARCADE_FILE, "rpg_enemy_xmas") + weapons = strings(ARCADE_FILE, "rpg_weapon_xmas") + else if(SSevents.holidays && SSevents.holidays[VALENTINES]) + name_action = pick_list(ARCADE_FILE, "rpg_action_valentines") + name_part1 = pick_list(ARCADE_FILE, "rpg_adjective_valentines") + name_part2 = pick_list(ARCADE_FILE, "rpg_enemy_valentines") + weapons = strings(ARCADE_FILE, "rpg_weapon_valentines") + else + name_action = pick_list(ARCADE_FILE, "rpg_action") + name_part1 = pick_list(ARCADE_FILE, "rpg_adjective") + name_part2 = pick_list(ARCADE_FILE, "rpg_enemy") + weapons = strings(ARCADE_FILE, "rpg_weapon") - name_part1 = pick("the Automatic ", "Farmer ", "Lord ", "Professor ", "the Cuban ", "the Evil ", "the Dread King ", "the Space ", "Lord ", "the Great ", "Duke ", "General ") - name_part2 = pick("Melonoid", "Murdertron", "Sorcerer", "Ruin", "Jeff", "Ectoplasm", "Crushulon", "Uhangoid", "Vhakoid", "Peteoid", "slime", "Griefer", "ERPer", "Lizard Man", "Unicorn", "Bloopers") + enemy_name = ("The " + name_part1 + " " + name_part2) + name = (name_action + " " + enemy_name) - enemy_name = replacetext((name_part1 + name_part2), "the ", "") - name = (name_action + name_part1 + name_part2) + enemy_setup(0) //in the case it's reset we assume the player skill is 0 because the VOID isn't a gamer /obj/machinery/computer/arcade/battle/ui_interact(mob/user) . = ..() + screen_setup(user) + +///sets up the main screen for the user +/obj/machinery/computer/arcade/battle/proc/screen_setup(mob/user) var/dat = "Close" dat += "

[enemy_name]

" - dat += "

[temp]

" + dat += "[temp]" dat += "
Health: [player_hp] | Magic: [player_mp] | Enemy Health: [enemy_hp]
" if (gameover) dat += "
New Game" else - dat += "
Attack | " - dat += "Heal | " - dat += "Recharge Power" + dat += "
Light attack" + dat += "
Defend" + dat += "
Counter attack" + dat += "
Power attack" dat += "
" - var/datum/browser/popup = new(user, "arcade", "Space Villain 2000") - popup.set_content(dat) - popup.open() + if(user.client) //mainly here to avoid a runtime when the player gets gibbed when losing the emag mode. + var/datum/browser/popup = new(user, "arcade", "Space Villain 2000") + popup.set_content(dat) + popup.open() /obj/machinery/computer/arcade/battle/Topic(href, href_list) if(..()) return + var/gamerSkill = 0 + // if(usr?.mind) + // gamerSkill = usr.mind.get_skill_level(/datum/skill/gaming) if (!blocked && !gameover) + var/attackamt = rand(5,7) + rand(0, gamerSkill) + + if(finishing_move) //time to bonk that fucker,cuban pete will sometime survive a finishing move. + attackamt *= 100 + + //light attack suck absolute ass but it doesn't cost any MP so it's pretty good to finish an enemy off if (href_list["attack"]) - blocked = TRUE - var/attackamt = rand(2,6) - temp = "You attack for [attackamt] damage!" - playsound(loc, 'sound/arcade/hit.ogg', 50, TRUE, extrarange = -3) - updateUsrDialog() - if(turtle > 0) - turtle-- - - sleep(turn_speed) + temp = "

you do quick jab for [attackamt] of damage!

" enemy_hp -= attackamt - arcade_action(usr) + arcade_action(usr,"attack",attackamt) - else if (href_list["heal"]) - blocked = TRUE - var/pointamt = rand(1,3) - var/healamt = rand(6,8) - temp = "You use [pointamt] magic to heal for [healamt] damage!" - playsound(loc, 'sound/arcade/heal.ogg', 50, TRUE, extrarange = -3) - updateUsrDialog() - turtle++ + //defend lets you gain back MP and take less damage from non magical attack. + else if(href_list["defend"]) + temp = "

you take a defensive stance and gain back 10 mp!

" + player_mp += 10 + arcade_action(usr,"defend",attackamt) + playsound(src, 'sound/arcade/mana.ogg', 50, TRUE, extrarange = -3) - sleep(turn_speed) - player_mp -= pointamt - player_hp += healamt - blocked = TRUE - updateUsrDialog() - arcade_action(usr) + //mainly used to counter short temper and their absurd damage, will deal twice the damage the player took of a non magical attack. + else if(href_list["counter_attack"] && player_mp >= 10) + temp = "

you prepare yourself to counter the next attack!

" + player_mp -= 10 + arcade_action(usr,"counter_attack",attackamt) + playsound(src, 'sound/arcade/mana.ogg', 50, TRUE, extrarange = -3) - else if (href_list["charge"]) - blocked = TRUE - var/chargeamt = rand(4,7) - temp = "You regain [chargeamt] points" - playsound(loc, 'sound/arcade/mana.ogg', 50, TRUE, extrarange = -3) - player_mp += chargeamt - if(turtle > 0) - turtle-- + else if(href_list["counter_attack"] && player_mp < 10) + temp = "

you don't have the mp necessary to counter attack and defend yourself instead

" + player_mp += 10 + arcade_action(usr,"defend",attackamt) + playsound(src, 'sound/arcade/mana.ogg', 50, TRUE, extrarange = -3) - updateUsrDialog() - sleep(turn_speed) - arcade_action(usr) + //power attack deals twice the amount of damage but is really expensive MP wise, mainly used with combos to get weakpoints. + else if (href_list["power_attack"] && player_mp >= 20) + temp = "

You attack [enemy_name] with all your might for [attackamt * 2] damage!

" + enemy_hp -= attackamt * 2 + player_mp -= 20 + arcade_action(usr,"power_attack",attackamt) + + else if(href_list["power_attack"] && player_mp < 20) + temp = "

You don't have the mp necessary for a power attack and settle for a light attack!

" + enemy_hp -= attackamt + arcade_action(usr,"attack",attackamt) if (href_list["close"]) usr.unset_machine() usr << browse(null, "window=arcade") else if (href_list["newgame"]) //Reset everything - temp = "New Round" - player_hp = initial(player_hp) - player_mp = initial(player_mp) - enemy_hp = initial(enemy_hp) - enemy_mp = initial(enemy_mp) - gameover = FALSE - turtle = 0 + temp = "

New Round

" if(obj_flags & EMAGGED) Reset() obj_flags &= ~EMAGGED + enemy_setup(gamerSkill) + screen_setup(usr) + + add_fingerprint(usr) updateUsrDialog() return -/obj/machinery/computer/arcade/battle/proc/arcade_action(mob/user) - if ((enemy_mp <= 0) || (enemy_hp <= 0)) +///happens after a player action and before the enemy turn. the enemy turn will be cancelled if there's a gameover. +/obj/machinery/computer/arcade/battle/proc/arcade_action(mob/user,player_stance,attackamt) + screen_setup(user) + blocked = TRUE + if(player_stance == "attack" || player_stance == "power_attack") + if(attackamt > 40) + playsound(src, 'sound/arcade/boom.ogg', 50, TRUE, extrarange = -3) + else + playsound(src, 'sound/arcade/hit.ogg', 50, TRUE, extrarange = -3) + + timer_id = addtimer(CALLBACK(src, .proc/enemy_action,player_stance,user),1 SECONDS,TIMER_STOPPABLE) + gameover_check(user) + +///the enemy turn, the enemy's action entirely depend on their current passive and a teensy tiny bit of randomness +/obj/machinery/computer/arcade/battle/proc/enemy_action(player_stance,mob/user) + var/list/list_temp = list() + + switch(LAZYLEN(last_three_move)) //we keep the last three action of the player in a list here + if(0 to 2) + LAZYADD(last_three_move, player_stance) + if(3) + for(var/i in 1 to 2) + last_three_move[i] = last_three_move[i + 1] + last_three_move[3] = player_stance + + if(4 to INFINITY) + last_three_move = null //this shouldn't even happen but we empty the list if it somehow goes above 3 + + var/enemy_stance + var/attack_amount = rand(8,10) //making the attack amount not vary too much so that it's easier to see if the enemy has a shotgun + + if(player_stance == "defend") + attack_amount -= 5 + + //if emagged, cuban pete will set up a bomb acting up as a timer. when it reaches 0 the player fucking dies + if(obj_flags & EMAGGED) + switch(bomb_cooldown--) + if(18) + list_temp += "

[enemy_name] takes two valve tank and links them together, what's he planning?

" + if(15) + list_temp += "

[enemy_name] adds a remote control to the tan- ho god is that a bomb?

" + if(12) + list_temp += "

[enemy_name] throws the bomb next to you, you'r too scared to pick it up.

" + if(6) + list_temp += "

[enemy_name]'s hand brushes the remote linked to the bomb, your heart skipped a beat.

" + if(2) + list_temp += "

[enemy_name] is going to press the button! It's now or never!

" + if(0) + player_hp -= attack_amount * 1000 //hey it's a maxcap we might as well go all in + + //yeah I used the shotgun as a passive, you know why? because the shotgun gives +5 attack which is pretty good + if(LAZYACCESS(enemy_passive, "shotgun")) + if(weakpoint_check("shotgun","defend","defend","power_attack")) + list_temp += "

You manage to disarm [enemy_name] with a surprise power attack and shoot him with his shotgun until it runs out of ammo!

" + enemy_hp -= 10 + chosen_weapon = "empty shotgun" + else + attack_amount += 5 + + //heccing chonker passive, only gives more HP at the start of a new game but has one of the hardest weakpoint to trigger. + if(LAZYACCESS(enemy_passive, "chonker")) + if(weakpoint_check("chonker","power_attack","power_attack","power_attack")) + list_temp += "

After a lot of power attacks you manage to tip over [enemy_name] as they fall over their enormous weight

" + enemy_hp -= 30 + + //smart passive trait, mainly works in tandem with other traits, makes the enemy unable to be counter_attacked + if(LAZYACCESS(enemy_passive, "smart")) + if(weakpoint_check("smart","defend","defend","attack")) + list_temp += "

[enemy_name] is confused by your illogical strategy!

" + attack_amount -= 5 + + else if(attack_amount >= player_hp) + player_hp -= attack_amount + list_temp += "

[enemy_name] figures out you are really close to death and finishes you off with their [chosen_weapon]!

" + enemy_stance = "attack" + + else if(player_stance == "counter_attack") + list_temp += "

[enemy_name] is not taking your bait.

" + if(LAZYACCESS(enemy_passive, "short_temper")) + list_temp += "However controlling their hatred of you still takes a toll on their mental and physical health!" + enemy_hp -= 5 + enemy_mp -= 5 + enemy_stance = "defensive" + + //short temper passive trait, gets easily baited into being counter attacked but will bypass your counter when low on HP + if(LAZYACCESS(enemy_passive, "short_temper")) + if(weakpoint_check("short_temper","counter_attack","counter_attack","counter_attack")) + list_temp += "

[enemy_name] is getting frustrated at all your counter attacks and throws a tantrum!

" + enemy_hp -= attack_amount + + else if(player_stance == "counter_attack") + if(!(LAZYACCESS(enemy_passive, "smart")) && enemy_hp > 30) + list_temp += "

[enemy_name] took the bait and allowed you to counter attack for [attack_amount * 2] damage!

" + player_hp -= attack_amount + enemy_hp -= attack_amount * 2 + enemy_stance = "attack" + + else if(enemy_hp <= 30) //will break through the counter when low enough on HP even when smart. + list_temp += "

[enemy_name] is getting tired of your tricks and breaks through your counter with their [chosen_weapon]!

" + player_hp -= attack_amount + enemy_stance = "attack" + + else if(!enemy_stance) + var/added_temp + + if(rand()) + added_temp = "you for [attack_amount + 5] damage!" + player_hp -= attack_amount + 5 + enemy_stance = "attack" + else + added_temp = "the wall, breaking their skull in the process and losing [attack_amount] hp!" //[enemy_name] you have a literal dent in your skull + enemy_hp -= attack_amount + enemy_stance = "attack" + + list_temp += "

[enemy_name] grits their teeth and charge right into [added_temp]

" + + //in the case none of the previous passive triggered, Mainly here to set an enemy stance for passives that needs it like the magical passive. + if(!enemy_stance) + enemy_stance = pick("attack","defensive") + if(enemy_stance == "attack") + player_hp -= attack_amount + list_temp += "

[enemy_name] attacks you for [attack_amount] points of damage with their [chosen_weapon]

" + if(player_stance == "counter_attack") + enemy_hp -= attack_amount * 2 + list_temp += "

You counter [enemy_name]'s attack and deal [attack_amount * 2] points of damage!

" + + if(enemy_stance == "defensive" && enemy_mp < 15) + list_temp += "

[enemy_name] take some time to get some mp back!

" + enemy_mp += attack_amount + + else if (enemy_stance == "defensive" && enemy_mp >= 15 && !(LAZYACCESS(enemy_passive, "magical"))) + list_temp += "

[enemy_name] quickly heal themselves for 5 hp!

" + enemy_mp -= 15 + enemy_hp += 5 + + //magical passive trait, recharges MP nearly every turn it's not blasting you with magic. + if(LAZYACCESS(enemy_passive, "magical")) + if(player_mp >= 50) + list_temp += "

the huge amount of magical energy you have acumulated throws [enemy_name] off balance!

" + enemy_mp = 0 + LAZYREMOVE(enemy_passive, "magical") + pissed_off++ + + else if(LAZYACCESS(enemy_passive, "smart") && player_stance == "counter_attack" && enemy_mp >= 20) + list_temp += "

[enemy_name] blasts you with magic from afar for 10 points of damage before you can counter!

" + player_hp -= 10 + enemy_mp -= 20 + + else if(enemy_hp >= 20 && enemy_mp >= 40 && enemy_stance == "defensive") + list_temp += "

[enemy_name] Blasts you with magic from afar!

" + enemy_mp -= 40 + player_hp -= 30 + enemy_stance = "attack" + + else if(enemy_hp < 20 && enemy_mp >= 20 && enemy_stance == "defensive") //it's a pretty expensive spell so they can't spam it that much + list_temp += "

[enemy_name] heal themselves with magic and gain back 20 hp!

" + enemy_hp += 20 + enemy_mp -= 30 + else + list_temp += "

[enemy_name]'s magical nature lets them get some mp back!

" + enemy_mp += attack_amount + + //poisonous passive trait, while it's less damage added than the shotgun it acts up even when the enemy doesn't attack at all. + if(LAZYACCESS(enemy_passive, "poisonous")) + if(weakpoint_check("poisonous","attack","attack","attack")) + list_temp += "

your flurry of attack throws back the poisonnous gas at [enemy_name] and makes them choke on it!

" + enemy_hp -= 5 + else + list_temp += "

the stinky breath of [enemy_name] hurts you for 3 hp!

" + player_hp -= 3 + + //if all passive's weakpoint have been triggered, set finishing_move to TRUE + if(pissed_off >= max_passive && !finishing_move) + list_temp += "

You have weakened [enemy_name] enough for them to show their weak point, you will do 10 times as much damage with your next attack!

" + finishing_move = TRUE + + playsound(src, 'sound/arcade/heal.ogg', 50, TRUE, extrarange = -3) + + temp = list_temp.Join() + gameover_check(user) + screen_setup(user) + blocked = FALSE + + +/obj/machinery/computer/arcade/battle/proc/gameover_check(mob/user) + var/xp_gained = 0 + if(enemy_hp <= 0) if(!gameover) + if(timer_id) + deltimer(timer_id) + timer_id = null + if(player_hp <= 0) + player_hp = 1 //let's just pretend the enemy didn't kill you so not both the player and enemy look dead. gameover = TRUE - temp = "[enemy_name] has fallen! Rejoice!" - playsound(loc, 'sound/arcade/win.ogg', 50, TRUE, extrarange = -3) + blocked = FALSE + temp = "

[enemy_name] has fallen! Rejoice!

" + playsound(loc, 'sound/arcade/win.ogg', 50, TRUE) if(obj_flags & EMAGGED) new /obj/effect/spawner/newbomb/timer/syndicate(loc) @@ -133,78 +402,76 @@ log_game("[key_name(usr)] has outbombed Cuban Pete and been awarded a bomb.") Reset() obj_flags &= ~EMAGGED + xp_gained += 100 else prizevend(user) + xp_gained += 50 SSblackbox.record_feedback("nested tally", "arcade_results", 1, list("win", (obj_flags & EMAGGED ? "emagged":"normal"))) - - else if ((obj_flags & EMAGGED) && (turtle >= 4)) - var/boomamt = rand(5,10) - temp = "[enemy_name] throws a bomb, exploding you for [boomamt] damage!" - playsound(loc, 'sound/arcade/boom.ogg', 50, TRUE, extrarange = -3) - player_hp -= boomamt - - else if ((enemy_mp <= 5) && (prob(70))) - var/stealamt = rand(2,3) - temp = "[enemy_name] steals [stealamt] of your power!" - playsound(loc, 'sound/arcade/steal.ogg', 50, TRUE, extrarange = -3) - player_mp -= stealamt - updateUsrDialog() - - if (player_mp <= 0) - gameover = TRUE - sleep(turn_speed) - temp = "You have been drained! GAME OVER" - playsound(loc, 'sound/arcade/lose.ogg', 50, TRUE, extrarange = -3) - if(obj_flags & EMAGGED) - usr.gib() - SSblackbox.record_feedback("nested tally", "arcade_results", 1, list("loss", "mana", (obj_flags & EMAGGED ? "emagged":"normal"))) - - else if ((enemy_hp <= 10) && (enemy_mp > 4)) - temp = "[enemy_name] heals for 4 health!" - playsound(loc, 'sound/arcade/heal.ogg', 50, TRUE, extrarange = -3) - enemy_hp += 4 - enemy_mp -= 4 - - else - var/attackamt = rand(3,6) - temp = "[enemy_name] attacks for [attackamt] damage!" - playsound(loc, 'sound/arcade/hit.ogg', 50, TRUE, extrarange = -3) - player_hp -= attackamt - - if ((player_mp <= 0) || (player_hp <= 0)) + else if(player_hp <= 0) + if(timer_id) + deltimer(timer_id) + timer_id = null gameover = TRUE - temp = "You have been crushed! GAME OVER" - playsound(loc, 'sound/arcade/lose.ogg', 50, TRUE, extrarange = -3) + temp = "

You have been crushed! GAME OVER

" + playsound(loc, 'sound/arcade/lose.ogg', 50, TRUE) + xp_gained += 10//pity points if(obj_flags & EMAGGED) - usr.gib() + var/mob/living/living_user = user + if (istype(living_user)) + living_user.gib() SSblackbox.record_feedback("nested tally", "arcade_results", 1, list("loss", "hp", (obj_flags & EMAGGED ? "emagged":"normal"))) - blocked = FALSE - return + // if(gameover) + // user?.mind?.adjust_experience(/datum/skill/gaming, xp_gained+1)//always gain at least 1 point of XP + + +///used to check if the last three move of the player are the one we want in the right order and if the passive's weakpoint has been triggered yet +/obj/machinery/computer/arcade/battle/proc/weakpoint_check(passive,first_move,second_move,third_move) + if(LAZYLEN(last_three_move) < 3) + return FALSE + + if(last_three_move[1] == first_move && last_three_move[2] == second_move && last_three_move[3] == third_move && LAZYACCESS(enemy_passive, passive)) + LAZYREMOVE(enemy_passive, passive) + pissed_off++ + return TRUE + else + return FALSE + + +/obj/machinery/computer/arcade/battle/Destroy() + enemy_passive = null + weapons = null + last_three_move = null + return ..() //well boys we did it, lists are no more /obj/machinery/computer/arcade/battle/examine_more(mob/user) - to_chat(user, "Scribbled on the side of the Arcade Machine you notice some writing...\ - \nmagical -> >=50 power\ - \nsmart -> defend, defend, light attack\ - \nshotgun -> defend, defend, power attack\ - \nshort temper -> counter, counter, counter\ - \npoisonous -> light attack, light attack, light attack\ - \nchonker -> power attack, power attack, power attack") - return ..() + var/list/msg = list("You notice some writing scribbled on the side of [src]...") + msg += "\tsmart -> defend, defend, light attack" + msg += "\tshotgun -> defend, defend, power attack" + msg += "\tshort temper -> counter, counter, counter" + msg += "\tpoisonous -> light attack, light attack, light attack" + msg += "\tchonker -> power attack, power attack, power attack" + return msg /obj/machinery/computer/arcade/battle/emag_act(mob/user) . = ..() if(obj_flags & EMAGGED) return + to_chat(user, "A mesmerizing Rhumba beat starts playing from the arcade machine's speakers!") - temp = "If you die in the game, you die for real!" - player_hp = 30 - player_mp = 10 - enemy_hp = 45 - enemy_mp = 20 + temp = "

If you die in the game, you die for real!

" + max_passive = 6 + bomb_cooldown = 18 + var/gamerSkill = 0 + // if(usr?.mind) + // gamerSkill = usr.mind.get_skill_level(/datum/skill/gaming) + enemy_setup(gamerSkill) + enemy_hp += 100 //extra HP just to make cuban pete even more bullshit + player_hp += 30 //the player will also get a few extra HP in order to have a fucking chance + + screen_setup(user) gameover = FALSE - blocked = FALSE obj_flags |= EMAGGED diff --git a/code/game/machinery/computer/arcade/minesweeper.dm b/code/game/machinery/computer/arcade/minesweeper.dm index 3f7ef778b0..dbd9890c81 100644 --- a/code/game/machinery/computer/arcade/minesweeper.dm +++ b/code/game/machinery/computer/arcade/minesweeper.dm @@ -268,7 +268,7 @@ visible_message("[src] dispenses [itemname]!", "You hear a chime and a clunk.") DISABLE_BITFIELD(obj_flags, EMAGGED) else - var/dope_prizes = (area >= 480) ? list(ARCADE_WEIGHT_RARE) : (area >= 256) ? list(ARCADE_WEIGHT_RARE, ARCADE_WEIGHT_TRICK) : null + var/dope_prizes = (area >= 480) ? 6 : (area >= 256) ? 4 : 2 prizevend(user, dope_prizes) if(game_status == MINESWEEPER_GAME_WON) @@ -311,12 +311,12 @@ var/new_rows = input(user, "How many rows do you want? (Minimum: 4, Maximum: 30)", "Minesweeper Rows") as null|num if(!new_rows || !user.canUseTopic(src, !hasSiliconAccessInArea(user))) return FALSE - new_rows = clamp(new_rows + 1, 4, 30) + new_rows = clamp(new_rows + 1, 4, 20) playsound(loc, 'sound/arcade/minesweeper_menuselect.ogg', 50, FALSE, extrarange = -3) var/new_columns = input(user, "How many columns do you want? (Minimum: 4, Maximum: 50)", "Minesweeper Squares") as null|num if(!new_columns || !user.canUseTopic(src, !hasSiliconAccessInArea(user))) return FALSE - new_columns = clamp(new_columns + 1, 4, 50) + new_columns = clamp(new_columns + 1, 4, 30) playsound(loc, 'sound/arcade/minesweeper_menuselect.ogg', 50, FALSE, extrarange = -3) var/grid_area = (new_rows - 1) * (new_columns - 1) var/lower_limit = round(grid_area*0.156) diff --git a/code/game/machinery/computer/arcade/misc_arcade.dm b/code/game/machinery/computer/arcade/misc_arcade.dm index 50633192ce..5c887e3726 100644 --- a/code/game/machinery/computer/arcade/misc_arcade.dm +++ b/code/game/machinery/computer/arcade/misc_arcade.dm @@ -8,7 +8,7 @@ icon_state = "arcade" circuit = /obj/item/circuitboard/computer/arcade/amputation -/obj/machinery/computer/arcade/amputation/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags) +/obj/machinery/computer/arcade/amputation/on_attack_hand(mob/user) if(!iscarbon(user)) return var/mob/living/carbon/c_user = user @@ -24,8 +24,13 @@ var/obj/item/bodypart/chopchop = c_user.get_bodypart(which_hand) chopchop.dismember() qdel(chopchop) - playsound(loc, 'sound/arcade/win.ogg', 50, TRUE, extrarange = -3) - for(var/i=1; i<=rand(3,5); i++) - prizevend(user) + // user.mind?.adjust_experience(/datum/skill/gaming, 100) + playsound(loc, 'sound/arcade/win.ogg', 50, TRUE) + prizevend(user, rand(3,5)) else to_chat(c_user, "You (wisely) decide against putting your hand in the machine.") + +/obj/machinery/computer/arcade/amputation/festive //dispenses wrapped gifts instead of arcade prizes, also known as the ancap christmas tree + name = "Mediborg's Festive Amputation Adventure" + desc = "A picture of a blood-soaked medical cyborg wearing a Santa hat flashes on the screen. The mediborg has a speech bubble that says, \"Put your hand in the machine if you aren't a coward!\"" + prize_override = list(/obj/item/a_gift/anything = 1) diff --git a/code/game/machinery/computer/arcade/orion_trail.dm b/code/game/machinery/computer/arcade/orion_trail.dm index 441010906c..58617a0cf3 100644 --- a/code/game/machinery/computer/arcade/orion_trail.dm +++ b/code/game/machinery/computer/arcade/orion_trail.dm @@ -1,5 +1,3 @@ - - // *** THE ORION TRAIL ** // #define ORION_TRAIL_WINTURN 9 @@ -15,6 +13,8 @@ #define ORION_TRAIL_COLLISION "Collision" #define ORION_TRAIL_SPACEPORT "Spaceport" #define ORION_TRAIL_BLACKHOLE "BlackHole" +#define ORION_TRAIL_OLDSHIP "Old Ship" +#define ORION_TRAIL_SEARCH "Old Ship Search" #define ORION_STATUS_START 1 #define ORION_STATUS_NORMAL 2 @@ -44,7 +44,8 @@ ORION_TRAIL_LING = 3, ORION_TRAIL_MALFUNCTION = 2, ORION_TRAIL_COLLISION = 1, - ORION_TRAIL_SPACEPORT = 2 + ORION_TRAIL_SPACEPORT = 2, + ORION_TRAIL_OLDSHIP = 2 ) var/list/stops = list() var/list/stopblurbs = list() @@ -55,13 +56,27 @@ var/gameStatus = ORION_STATUS_START var/canContinueEvent = 0 + var/obj/item/radio/Radio + var/list/gamers = list() + var/killed_crew = 0 + + +/obj/machinery/computer/arcade/orion_trail/Initialize() + . = ..() + Radio = new /obj/item/radio(src) + Radio.listening = 0 + +/obj/machinery/computer/arcade/orion_trail/Destroy() + QDEL_NULL(Radio) + return ..() + /obj/machinery/computer/arcade/orion_trail/kobayashi name = "Kobayashi Maru control computer" desc = "A test for cadets" icon = 'icons/obj/machines/particle_accelerator.dmi' icon_state = "control_boxp" events = list("Raiders" = 3, "Interstellar Flux" = 1, "Illness" = 3, "Breakdown" = 2, "Malfunction" = 2, "Collision" = 1, "Spaceport" = 2) - prizes = list(/obj/item/paper/fluff/holodeck/trek_diploma = 1) + prize_override = list(/obj/item/paper/fluff/holodeck/trek_diploma = 1) settlers = list("Kirk","Worf","Gene") /obj/machinery/computer/arcade/orion_trail/Reset() @@ -96,14 +111,52 @@ event = null gameStatus = ORION_STATUS_NORMAL lings_aboard = 0 + killed_crew = 0 //spaceport junk spaceport_raided = 0 spaceport_freebie = 0 last_spaceport_action = "" -/obj/machinery/computer/arcade/orion_trail/ui_interact(mob/user) +/obj/machinery/computer/arcade/orion_trail/proc/report_player(mob/gamer) + if(gamers[gamer] == -2) + return // enough harassing them + + if(gamers[gamer] == -1) + say("WARNING: Continued antisocial behavior detected: Dispensing self-help literature.") + new /obj/item/paper/pamphlet/violent_video_games(drop_location()) + gamers[gamer]-- + return + + if(!(gamer in gamers)) + gamers[gamer] = 0 + + gamers[gamer]++ // How many times the player has 'prestiged' (massacred their crew) + + if(gamers[gamer] > 2 && prob(20 * gamers[gamer])) + + Radio.set_frequency(FREQ_SECURITY) + Radio.talk_into(src, "SECURITY ALERT: Crewmember [gamer] recorded displaying antisocial tendencies in [get_area(src)]. Please watch for violent behavior.", FREQ_SECURITY) + + Radio.set_frequency(FREQ_MEDICAL) + Radio.talk_into(src, "PSYCH ALERT: Crewmember [gamer] recorded displaying antisocial tendencies in [get_area(src)]. Please schedule psych evaluation.", FREQ_MEDICAL) + + gamers[gamer] = -1 + + gamer.client.give_award(/datum/award/achievement/misc/gamer, gamer) // PSYCH REPORT NOTE: patient kept rambling about how they did it for an "achievement", recommend continued holding for observation + // gamer.mind?.adjust_experience(/datum/skill/gaming, 50) // cheevos make u better + + if(!isnull(GLOB.data_core.general)) + for(var/datum/data/record/R in GLOB.data_core.general) + if(R.fields["name"] == gamer.name) + R.fields["m_stat"] = "*Unstable*" + return + +/obj/machinery/computer/arcade/orion_trail/ui_interact(mob/_user) . = ..() + if (!isliving(_user)) + return + var/mob/living/user = _user if(fuel <= 0 || food <=0 || settlers.len == 0) gameStatus = ORION_STATUS_GAMEOVER event = null @@ -136,6 +189,8 @@ desc = "Learn how our ancestors got to Orion, and have fun in the process!" dat += "

May They Rest In Peace

" + // user?.mind?.adjust_experience(/datum/skill/gaming, 10)//learning from your mistakes is the first rule of roguelikes + else if(event) dat = eventdat else if(gameStatus == ORION_STATUS_NORMAL) @@ -174,20 +229,32 @@ return busy = TRUE + // var/gamerSkillLevel = 0 + var/gamerSkill = 0 + var/gamerSkillRands = 0 + + // if(usr?.mind) + // gamerSkillLevel = usr.mind.get_skill_level(/datum/skill/gaming) + // gamerSkill = usr.mind.get_skill_modifier(/datum/skill/gaming, SKILL_PROBS_MODIFIER) + // gamerSkillRands = usr.mind.get_skill_modifier(/datum/skill/gaming, SKILL_RANDS_MODIFIER) + + + var/xp_gained = 0 if (href_list["continue"]) //Continue your travels if(gameStatus == ORION_STATUS_NORMAL && !event && turns != 7) if(turns >= ORION_TRAIL_WINTURN) win(usr) + xp_gained += 34 else food -= (alive+lings_aboard)*2 fuel -= 5 - if(turns == 2 && prob(30)) + if(turns == 2 && prob(30-gamerSkill)) event = ORION_TRAIL_COLLISION event() - else if(prob(75)) + else if(prob(75-gamerSkill)) event = pickweight(events) if(lings_aboard) - if(event == ORION_TRAIL_LING || prob(55)) + if(event == ORION_TRAIL_LING || prob(55-gamerSkill)) event = ORION_TRAIL_LING_ATTACK event() turns += 1 @@ -195,15 +262,18 @@ var/mob/living/carbon/M = usr //for some vars switch(event) if(ORION_TRAIL_RAIDERS) - if(prob(50)) + if(prob(50-gamerSkill)) to_chat(usr, "You hear battle shouts. The tramping of boots on cold metal. Screams of agony. The rush of venting air. Are you going insane?") M.hallucination += 30 else to_chat(usr, "Something strikes you from behind! It hurts like hell and feel like a blunt weapon, but nothing is there...") M.take_bodypart_damage(30) - playsound(loc, 'sound/weapons/genhit2.ogg', 100, 1) + playsound(loc, 'sound/weapons/genhit2.ogg', 100, TRUE) if(ORION_TRAIL_ILLNESS) - var/severity = rand(1,3) //pray to RNGesus. PRAY, PIGS + var/maxSeverity = 3 + // if(gamerSkillLevel >= SKILL_LEVEL_EXPERT) + // maxSeverity = 2 //part of gitting gud is rng mitigation + var/severity = rand(1,maxSeverity) //pray to RNGesus. PRAY, PIGS if(severity == 1) to_chat(M, "You suddenly feel slightly nauseated." ) if(severity == 2) @@ -215,16 +285,16 @@ sleep(30) M.vomit(10, distance = 5) if(ORION_TRAIL_FLUX) - if(prob(75)) + if(prob(75-gamerSkill)) M.DefaultCombatKnockdown(60) say("A sudden gust of powerful wind slams [M] into the floor!") M.take_bodypart_damage(25) - playsound(loc, 'sound/weapons/genhit.ogg', 100, 1) + playsound(loc, 'sound/weapons/genhit.ogg', 100, TRUE) else to_chat(M, "A violent gale blows past you, and you barely manage to stay standing!") if(ORION_TRAIL_COLLISION) //by far the most damaging event - if(prob(90)) - playsound(loc, 'sound/effects/bang.ogg', 100, 1) + if(prob(90-gamerSkill)) + playsound(loc, 'sound/effects/bang.ogg', 100, TRUE) var/turf/open/floor/F for(F in orange(1, src)) F.ScrapeAway() @@ -232,15 +302,15 @@ if(hull) sleep(10) say("A new floor suddenly appears around [src]. What the hell?") - playsound(loc, 'sound/weapons/genhit.ogg', 100, 1) + playsound(loc, 'sound/weapons/genhit.ogg', 100, TRUE) var/turf/open/space/T for(T in orange(1, src)) T.PlaceOnTop(/turf/open/floor/plating) else say("Something slams into the floor around [src] - luckily, it didn't get through!") - playsound(loc, 'sound/effects/bang.ogg', 50, 1) + playsound(loc, 'sound/effects/bang.ogg', 50, TRUE) if(ORION_TRAIL_MALFUNCTION) - playsound(loc, 'sound/effects/empulse.ogg', 50, 1) + playsound(loc, 'sound/effects/empulse.ogg', 50, TRUE) visible_message("[src] malfunctions, randomizing in-game stats!") var/oldfood = food var/oldfuel = fuel @@ -254,7 +324,7 @@ audible_message("[src] lets out a somehow ominous chime.") food = oldfood fuel = oldfuel - playsound(loc, 'sound/machines/chime.ogg', 50, 1) + playsound(loc, 'sound/machines/chime.ogg', 50, TRUE) else if(href_list["newgame"]) //Reset everything if(gameStatus == ORION_STATUS_START) @@ -266,6 +336,10 @@ food = 80 fuel = 60 settlers = list("Harry","Larry","Bob") + else if(href_list["search"]) //search old ship + if(event == ORION_TRAIL_OLDSHIP) + event = ORION_TRAIL_SEARCH + event() else if(href_list["slow"]) //slow down if(event == ORION_TRAIL_FLUX) food -= (alive+lings_aboard)*2 @@ -302,11 +376,11 @@ event = null else if(href_list["blackhole"]) //keep speed past a black hole if(turns == 7) - if(prob(75)) + if(prob(75-gamerSkill)) event = ORION_TRAIL_BLACKHOLE event() if(obj_flags & EMAGGED) - playsound(loc, 'sound/effects/supermatter.ogg', 100, 1) + playsound(loc, 'sound/effects/supermatter.ogg', 100, TRUE) say("A miniature black hole suddenly appears in front of [src], devouring [usr] alive!") if(isliving(usr)) var/mob/living/L = usr @@ -328,22 +402,29 @@ else if(href_list["killcrew"]) //shoot a crewmember if(gameStatus == ORION_STATUS_NORMAL || event == ORION_TRAIL_LING) var/sheriff = remove_crewmember() //I shot the sheriff - playsound(loc,'sound/weapons/gunshot.ogg', 100, 1) + playsound(loc,'sound/weapons/gunshot.ogg', 100, TRUE) + killed_crew++ + + var/mob/living/user = usr if(settlers.len == 0 || alive == 0) say("The last crewmember [sheriff], shot themselves, GAME OVER!") if(obj_flags & EMAGGED) - usr.death(0) - obj_flags &= EMAGGED + user.death(FALSE) gameStatus = ORION_STATUS_GAMEOVER event = null + + if(killed_crew >= 4) + xp_gained -= 15//no cheating by spamming game overs + report_player(usr) else if(obj_flags & EMAGGED) if(usr.name == sheriff) say("The crew of the ship chose to kill [usr.name]!") - usr.death(0) + user.death(FALSE) if(event == ORION_TRAIL_LING) //only ends the ORION_TRAIL_LING event, since you can do this action in multiple places event = null + killed_crew-- // the kill was valid //Spaceport specific interactions //they get a header because most of them don't reset event (because it's a shop, you leave when you want to) @@ -356,6 +437,7 @@ fuel -= 10 food -= 10 event() + killed_crew-- // I mean not really but you know else if(href_list["sellcrew"]) //sell a crewmember if(gameStatus == ORION_STATUS_MARKET) @@ -377,15 +459,16 @@ else if(href_list["raid_spaceport"]) if(gameStatus == ORION_STATUS_MARKET) if(!spaceport_raided) - var/success = min(15 * alive,100) //default crew (4) have a 60% chance + var/success = min(15 * alive + gamerSkill,100) //default crew (4) have a 60% chance spaceport_raided = 1 var/FU = 0 var/FO = 0 if(prob(success)) - FU = rand(5,15) - FO = rand(5,15) + FU = rand(5 + gamerSkillRands,15 + gamerSkillRands) + FO = rand(5 + gamerSkillRands,15 + gamerSkillRands) last_spaceport_action = "You successfully raided the spaceport! You gained [FU] Fuel and [FO] Food! (+[FU]FU,+[FO]FO)" + xp_gained += 10 else FU = rand(-5,-15) FO = rand(-5,-15) @@ -444,8 +527,7 @@ add_fingerprint(usr) updateUsrDialog() busy = FALSE - return - + // usr?.mind?.adjust_experience(/datum/skill/gaming, xp_gained+1) /obj/machinery/computer/arcade/orion_trail/proc/event() eventdat = "

[event]

" @@ -474,6 +556,38 @@ eventdat += "

Slow Down Continue

" eventdat += "

Close

" + if(ORION_TRAIL_OLDSHIP) + eventdat += "
Your crew spots an old ship floating through space. It might have some supplies, but then again it looks rather unsafe." + eventdat += "

Search itLeave it

Close

" + canContinueEvent = 1 + + if(ORION_TRAIL_SEARCH) + switch(rand(100)) + if(0 to 15) + var/rescued = add_crewmember() + var/oldfood = rand(1,7) + var/oldfuel = rand(4,10) + food += oldfood + fuel += oldfuel + eventdat += "
As you look through it you find some supplies and a living person!" + eventdat += "
[rescued] was rescued from the abandoned ship!" + eventdat += "
You found [oldfood] Food and [oldfuel] Fuel." + if(15 to 35) + var/lfuel = rand(4,7) + var/deadname = remove_crewmember() + fuel -= lfuel + eventdat += "
[deadname] was lost deep in the wreckage, and your own vessel lost [lfuel] Fuel maneuvering to the the abandoned ship." + if(35 to 65) + var/oldfood = rand(5,11) + food += oldfood + engine++ + eventdat += "
You found [oldfood] Food and some parts amongst the wreck." + else + eventdat += "
As you look through the wreck you cannot find much of use." + eventdat += "

Continue

" + eventdat += "

Close

" + canContinueEvent = 1 + if(ORION_TRAIL_ILLNESS) eventdat += "A deadly illness has been contracted!" var/deadname = remove_crewmember() @@ -687,7 +801,7 @@ //Add Random/Specific crewmember -/obj/machinery/computer/arcade/orion_trail/proc/add_crewmember(var/specific = "") +/obj/machinery/computer/arcade/orion_trail/proc/add_crewmember(specific = "") var/newcrew = "" if(specific) newcrew = specific @@ -703,7 +817,7 @@ //Remove Random/Specific crewmember -/obj/machinery/computer/arcade/orion_trail/proc/remove_crewmember(var/specific = "", var/dont_remove = "") +/obj/machinery/computer/arcade/orion_trail/proc/remove_crewmember(specific = "", dont_remove = "") var/list/safe2remove = settlers var/removed = "" if(dont_remove) @@ -779,14 +893,14 @@ to_chat(user, "You flip the switch on the underside of [src].") active = 1 visible_message("[src] softly beeps and whirs to life!") - playsound(loc, 'sound/machines/defib_SaftyOn.ogg', 25, 1) + playsound(loc, 'sound/machines/defib_SaftyOn.ogg', 25, TRUE) say("This is ship ID #[rand(1,1000)] to Orion Port Authority. We're coming in for landing, over.") sleep(20) visible_message("[src] begins to vibrate...") say("Uh, Port? Having some issues with our reactor, could you check it out? Over.") sleep(30) say("Oh, God! Code Eight! CODE EIGHT! IT'S GONNA BL-") - playsound(loc, 'sound/machines/buzz-sigh.ogg', 25, 1) + playsound(loc, 'sound/machines/buzz-sigh.ogg', 25, TRUE) sleep(3.6) visible_message("[src] explodes!") explosion(loc, 2,4,8, flame_range = 16) diff --git a/code/game/machinery/computer/atmos_control.dm b/code/game/machinery/computer/atmos_control.dm index 75a181b922..98d8811948 100644 --- a/code/game/machinery/computer/atmos_control.dm +++ b/code/game/machinery/computer/atmos_control.dm @@ -92,6 +92,7 @@ GLOBAL_LIST_EMPTY(atmos_air_controllers) icon_screen = "tank" icon_keyboard = "atmos_key" circuit = /obj/item/circuitboard/computer/atmos_control + light_color = LIGHT_COLOR_CYAN var/frequency = FREQ_ATMOS_STORAGE var/list/sensors = list( @@ -102,6 +103,20 @@ GLOBAL_LIST_EMPTY(atmos_air_controllers) ATMOS_GAS_MONITOR_SENSOR_N2O = "Nitrous Oxide Tank", ATMOS_GAS_MONITOR_SENSOR_AIR = "Mixed Air Tank", ATMOS_GAS_MONITOR_SENSOR_MIX = "Mix Tank", + // ATMOS_GAS_MONITOR_SENSOR_BZ = "BZ Tank", + // ATMOS_GAS_MONITOR_SENSOR_FREON = "Freon Tank", + // ATMOS_GAS_MONITOR_SENSOR_HALON = "Halon Tank", + // ATMOS_GAS_MONITOR_SENSOR_HEALIUM = "Healium Tank", + // ATMOS_GAS_MONITOR_SENSOR_H2 = "Hydrogen Tank", + // ATMOS_GAS_MONITOR_SENSOR_HYPERNOBLIUM = "Hypernoblium Tank", + // ATMOS_GAS_MONITOR_SENSOR_MIASMA = "Miasma Tank", + // ATMOS_GAS_MONITOR_SENSOR_NO2 = "Nitryl Tank", + // ATMOS_GAS_MONITOR_SENSOR_PLUOXIUM = "Pluoxium Tank", + // ATMOS_GAS_MONITOR_SENSOR_PROTO_NITRATE = "Proto-Nitrate Tank", + // ATMOS_GAS_MONITOR_SENSOR_STIMULUM = "Stimulum Tank", + // ATMOS_GAS_MONITOR_SENSOR_TRITIUM = "Tritium Tank", + // ATMOS_GAS_MONITOR_SENSOR_H2O = "Water Vapor Tank", + // ATMOS_GAS_MONITOR_SENSOR_ZAUKER = "Zauker Tank", ATMOS_GAS_MONITOR_LOOP_DISTRIBUTION = "Distribution Loop", ATMOS_GAS_MONITOR_LOOP_ATMOS_WASTE = "Atmos Waste Loop", ATMOS_GAS_MONITOR_SENSOR_INCINERATOR = "Incinerator Chamber", @@ -110,7 +125,6 @@ GLOBAL_LIST_EMPTY(atmos_air_controllers) var/list/sensor_information = list() var/datum/radio_frequency/radio_connection - light_color = LIGHT_COLOR_CYAN /obj/machinery/computer/atmos_control/Initialize() . = ..() @@ -165,15 +179,10 @@ GLOBAL_LIST_EMPTY(atmos_air_controllers) /obj/machinery/computer/atmos_control/incinerator name = "Incinerator Air Control" sensors = list(ATMOS_GAS_MONITOR_SENSOR_INCINERATOR = "Incinerator Chamber") - ui_x = 400 - ui_y = 300 - //Toxins mix sensor only /obj/machinery/computer/atmos_control/toxinsmix name = "Toxins Mixing Air Control" sensors = list(ATMOS_GAS_MONITOR_SENSOR_TOXINS_LAB = "Toxins Mixing Chamber") - ui_x = 400 - ui_y = 300 ///////////////////////////////////////////////////////////// // LARGE TANK CONTROL @@ -184,13 +193,9 @@ GLOBAL_LIST_EMPTY(atmos_air_controllers) var/output_tag frequency = FREQ_ATMOS_STORAGE circuit = /obj/item/circuitboard/computer/atmos_control/tank - var/list/input_info var/list/output_info - ui_x = 500 - ui_y = 315 - /obj/machinery/computer/atmos_control/tank/oxygen_tank name = "Oxygen Supply Control" input_tag = ATMOS_GAS_MONITOR_INPUT_O2 @@ -236,7 +241,7 @@ GLOBAL_LIST_EMPTY(atmos_air_controllers) // This hacky madness is the evidence of the fact that a lot of machines were never meant to be constructable, im so sorry you had to see this /obj/machinery/computer/atmos_control/tank/proc/reconnect(mob/user) var/list/IO = list() - var/datum/radio_frequency/freq = SSradio.return_frequency(FREQ_ATMOS_STORAGE) + var/datum/radio_frequency/freq = SSradio.return_frequency(frequency) var/list/devices = freq.devices["_default"] for(var/obj/machinery/atmospherics/components/unary/vent_pump/U in devices) var/list/text = splittext(U.id_tag, "_") @@ -252,10 +257,11 @@ GLOBAL_LIST_EMPTY(atmos_air_controllers) src.output_tag = "[S]_out" name = "[uppertext(S)] Supply Control" var/list/new_devices = freq.devices["4"] + sensors.Cut() for(var/obj/machinery/air_sensor/U in new_devices) var/list/text = splittext(U.id_tag, "_") if(text[1] == S) - sensors = list("[S]_sensor" = "Tank") + sensors = list("[S]_sensor" = "[S] Tank") break for(var/obj/machinery/atmospherics/components/unary/outlet_injector/U in devices) @@ -268,13 +274,16 @@ GLOBAL_LIST_EMPTY(atmos_air_controllers) data["tank"] = TRUE data["inputting"] = input_info ? input_info["power"] : FALSE data["inputRate"] = input_info ? input_info["volume_rate"] : 0 + data["maxInputRate"] = input_info ? MAX_TRANSFER_RATE : 0 data["outputting"] = output_info ? output_info["power"] : FALSE data["outputPressure"] = output_info ? output_info["internal"] : 0 - + data["maxOutputPressure"] = output_info ? MAX_OUTPUT_PRESSURE : 0 return data /obj/machinery/computer/atmos_control/tank/ui_act(action, params) - if(..() || !radio_connection) + . = ..() + + if(. || !radio_connection) return var/datum/signal/signal = new(list("sigtype" = "command", "user" = usr)) switch(action) diff --git a/code/game/machinery/computer/camera.dm b/code/game/machinery/computer/camera.dm index 03a5a17493..a62d2cd102 100644 --- a/code/game/machinery/computer/camera.dm +++ b/code/game/machinery/computer/camera.dm @@ -1,3 +1,5 @@ +#define DEFAULT_MAP_SIZE 15 + /obj/machinery/computer/security name = "security camera console" desc = "Used to access the various cameras on the station." @@ -8,15 +10,19 @@ 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/const/default_map_size = 15 - var/obj/screen/cam_screen - var/obj/screen/plane_master/lighting/cam_plane_master + 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 + interaction_flags_machine = INTERACT_MACHINE_ALLOW_SILICON | INTERACT_MACHINE_SET_MACHINE //| INTERACT_MACHINE_REQUIRES_SIGHT + /obj/machinery/computer/security/Initialize() . = ..() // Map name has to start and end with an A-Z character, @@ -33,18 +39,20 @@ cam_screen.assigned_map = map_name cam_screen.del_on_map_removal = FALSE cam_screen.screen_loc = "[map_name]:1,1" - cam_plane_master = new - cam_plane_master.name = "plane_master" - cam_plane_master.assigned_map = map_name - cam_plane_master.del_on_map_removal = FALSE - cam_plane_master.screen_loc = "[map_name]:CENTER" + 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 /obj/machinery/computer/security/Destroy() qdel(cam_screen) - qdel(cam_plane_master) + QDEL_LIST(cam_plane_masters) qdel(cam_background) return ..() @@ -56,9 +64,10 @@ /obj/machinery/computer/security/ui_interact(mob/user, datum/tgui/ui) // Update UI ui = SStgui.try_update_ui(user, src, ui) - // Show static if can't use the camera - if(!active_camera?.can_use()) - show_camera_static() + + // Update the camera, showing static if necessary and updating data if the location has moved. + update_active_camera_screen() + if(!ui) var/user_ref = REF(user) var/is_living = isliving(user) @@ -72,7 +81,7 @@ use_power(active_power_usage) // Register map objects user.client.register_map_obj(cam_screen) - for(var/plane in cam_plane_master) + for(var/plane in cam_plane_masters) user.client.register_map_obj(plane) user.client.register_map_obj(cam_background) // Open UI @@ -100,6 +109,7 @@ data["cameras"] += list(list( name = C.c_tag, )) + return data /obj/machinery/computer/security/ui_act(action, params) @@ -110,31 +120,51 @@ if(action == "switch_camera") var/c_tag = params["name"] var/list/cameras = get_available_cameras() - var/obj/machinery/camera/C = cameras[c_tag] - active_camera = C + var/obj/machinery/camera/selected_camera = cameras[c_tag] + active_camera = selected_camera playsound(src, get_sfx("terminal_type"), 25, FALSE) - // Show static if can't use the camera - if(!active_camera?.can_use()) - show_camera_static() + if(!selected_camera) return TRUE - var/list/visible_turfs = list() - for(var/turf/T in (C.isXRay() \ - ? range(C.view_range, C) \ - : view(C.view_range, C))) - visible_turfs += T - - var/list/bbox = get_bbox_of_atoms(visible_turfs) - var/size_x = bbox[3] - bbox[1] + 1 - var/size_y = bbox[4] - bbox[2] + 1 - - cam_screen.vis_contents = visible_turfs - cam_background.icon_state = "clear" - cam_background.fill_rect(1, 1, size_x, size_y) + update_active_camera_screen() return TRUE +/obj/machinery/computer/security/proc/update_active_camera_screen() + // Show static if can't use the camera + if(!active_camera?.can_use()) + show_camera_static() + return + + 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) + /obj/machinery/computer/security/ui_close(mob/user) var/user_ref = REF(user) var/is_living = isliving(user) @@ -151,7 +181,7 @@ /obj/machinery/computer/security/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) + cam_background.fill_rect(1, 1, DEFAULT_MAP_SIZE, DEFAULT_MAP_SIZE) // Returns the list of cameras accessible from this computer /obj/machinery/computer/security/proc/get_available_cameras() @@ -179,7 +209,7 @@ name = "security camera monitor" desc = "An old TV hooked into the station's camera network." icon_state = "television" - icon_keyboard = null + icon_keyboard = "no_keyboard" icon_screen = "detective_tv" pass_flags = PASSTABLE @@ -211,7 +241,7 @@ /obj/machinery/computer/security/qm name = "\improper Quartermaster's camera console" - desc = "A console with access to the mining, auxillary base and vault camera networks." + desc = "A console with access to the mining, auxiliary base and vault camera networks." network = list("mine", "auxbase", "vault") circuit = null @@ -222,17 +252,12 @@ desc = "Used for watching an empty arena." icon = 'icons/obj/stationobjs.dmi' icon_state = "telescreen" + layer = SIGN_LAYER network = list("thunder") density = FALSE circuit = null light_power = 0 -/obj/machinery/computer/security/telescreen/Initialize() - . = ..() - var/turf/T = get_turf_pixel(src) - if(iswallturf(T)) - plane = ABOVE_WALL_PLANE - /obj/machinery/computer/security/telescreen/update_icon_state() icon_state = initial(icon_state) if(stat & BROKEN) @@ -246,21 +271,19 @@ network = list("thunder") density = FALSE circuit = null - //interaction_flags_atom = NONE // interact() is called by BigClick() + interaction_flags_atom = NONE // interact() is called by BigClick() var/icon_state_off = "entertainment_blank" var/icon_state_on = "entertainment" -/* If someone would like to try to get this long-distance viewing thing working, be my guest. I tried everything I could possibly think of and it just refused to operate correctly. - /obj/machinery/computer/security/telescreen/entertainment/Initialize() . = ..() RegisterSignal(src, COMSIG_CLICK, .proc/BigClick) // Bypass clickchain to allow humans to use the telescreen from a distance /obj/machinery/computer/security/telescreen/entertainment/proc/BigClick() - interact(usr) + SIGNAL_HANDLER -*/ + INVOKE_ASYNC(src, /atom.proc/interact, usr) /obj/machinery/computer/security/telescreen/entertainment/proc/notify(on) if(on && icon_state == icon_state_off) @@ -279,8 +302,8 @@ network = list("rd", "aicore", "aiupload", "minisat", "xeno", "test") /obj/machinery/computer/security/telescreen/circuitry - name = "circuitry telescreen" - desc = "Used for watching the other eggheads from the safety of the circuitry lab." + name = "research telescreen" + desc = "A telescreen with access to the research division's camera network." network = list("rd") /obj/machinery/computer/security/telescreen/ce @@ -324,8 +347,8 @@ network = list("prison") /obj/machinery/computer/security/telescreen/auxbase - name = "auxillary base monitor" - desc = "A telescreen that connects to the auxillary base's camera." + name = "auxiliary base monitor" + desc = "A telescreen that connects to the auxiliary base's camera." network = list("auxbase") /obj/machinery/computer/security/telescreen/minisat @@ -346,3 +369,5 @@ for(var/i in network) network -= i network += "[idnum][i]" + +#undef DEFAULT_MAP_SIZE diff --git a/code/game/machinery/computer/communications.dm b/code/game/machinery/computer/communications.dm index 3ef887b156..ff65a6e159 100755 --- a/code/game/machinery/computer/communications.dm +++ b/code/game/machinery/computer/communications.dm @@ -1,14 +1,10 @@ -#define STATE_DEFAULT 1 -#define STATE_CALLSHUTTLE 2 -#define STATE_CANCELSHUTTLE 3 -#define STATE_MESSAGELIST 4 -#define STATE_VIEWMESSAGE 5 -#define STATE_DELMESSAGE 6 -#define STATE_STATUSDISPLAY 7 -#define STATE_ALERT_LEVEL 8 -#define STATE_CONFIRM_LEVEL 9 -#define STATE_TOGGLE_EMERGENCY 10 -#define STATE_PURCHASE 11 +#define IMPORTANT_ACTION_COOLDOWN (60 SECONDS) +#define MAX_STATUS_LINE_LENGTH 40 + +#define STATE_BUYING_SHUTTLE "buying_shuttle" +#define STATE_CHANGING_STATUS "changing_status" +#define STATE_MAIN "main" +#define STATE_MESSAGES "messages" // The communications computer /obj/machinery/computer/communications @@ -19,423 +15,53 @@ req_access = list(ACCESS_HEADS) circuit = /obj/item/circuitboard/computer/communications light_color = LIGHT_COLOR_BLUE - var/auth_id = "Unknown" //Who is currently logged in? - var/list/datum/comm_message/messages = list() - var/datum/comm_message/currmsg - var/datum/comm_message/aicurrmsg - var/state = STATE_DEFAULT - var/aistate = STATE_DEFAULT - var/message_cooldown = 0 - var/ai_message_cooldown = 0 - var/tmp_alertlevel = 0 - var/static/security_level_cd // used to stop mass spam. - var/stat_msg1 - var/stat_msg2 + /// Cooldown for important actions, such as messaging CentCom or other sectors + COOLDOWN_DECLARE(static/important_action_cooldown) + /// The current state of the UI + var/state = STATE_MAIN -/obj/machinery/computer/communications/proc/checkCCcooldown() - var/obj/item/circuitboard/computer/communications/CM = circuit - if(CM.lastTimeUsed + 600 > world.time) - return FALSE - return TRUE + /// The current state of the UI for AIs + var/cyborg_state = STATE_MAIN + + /// The name of the user who logged in + var/authorize_name + + /// The access that the card had on login + var/list/authorize_access + + /// The messages this console has been sent + var/list/datum/comm_message/messages + + /// How many times the alert level has been changed + /// Used to clear the modal to change alert level + var/alert_level_tick = 0 + + /// The last lines used for changing the status display + var/static/last_status_display /obj/machinery/computer/communications/Initialize() . = ..() GLOB.shuttle_caller_list += src -/obj/machinery/computer/communications/Topic(href, href_list) - if(..()) - return - if(!usr.canUseTopic(src, !issilicon(usr))) - return - if(!is_station_level(z) && !is_reserved_level(z)) //Can only use in transit and on SS13 - to_chat(usr, "Unable to establish a connection: \black You're too far away from the station!") - return - usr.set_machine(src) +/// Are we NOT a silicon, AND we're logged in as the captain? +/obj/machinery/computer/communications/proc/authenticated_as_non_silicon_captain(mob/user) + if (issilicon(user)) + return FALSE + return ACCESS_CAPTAIN in authorize_access +/// Are we a silicon, OR we're logged in as the captain? +/obj/machinery/computer/communications/proc/authenticated_as_silicon_or_captain(mob/user) + if (issilicon(user)) + return TRUE + return ACCESS_CAPTAIN in authorize_access - if(!href_list["operation"]) - return - var/obj/item/circuitboard/computer/communications/CM = circuit - switch(href_list["operation"]) - // main interface - if("main") - state = STATE_DEFAULT - playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, FALSE) - if("login") - var/mob/M = usr - - var/obj/item/card/id/I = M.get_idcard(TRUE) - - if(I && istype(I)) - if(check_access(I)) - authenticated = 1 - auth_id = "[I.registered_name] ([I.assignment])" - if((ACCESS_CAPTAIN in I.access)) - authenticated = 2 - playsound(src, 'sound/machines/terminal_on.ogg', 50, FALSE) - if(obj_flags & EMAGGED) - authenticated = 2 - auth_id = "Unknown" - to_chat(M, "[src] lets out a quiet alarm as its login is overridden.") - playsound(src, 'sound/machines/terminal_on.ogg', 50, FALSE) - playsound(src, 'sound/machines/terminal_alert.ogg', 25, FALSE) - if(prob(25)) - for(var/mob/living/silicon/ai/AI in active_ais()) - SEND_SOUND(AI, sound('sound/machines/terminal_alert.ogg', volume = 10)) //Very quiet for balance reasons - if("logout") - authenticated = 0 - playsound(src, 'sound/machines/terminal_off.ogg', 50, FALSE) - - if("swipeidseclevel") - var/mob/M = usr - var/obj/item/card/id/I = M.get_active_held_item() - if (istype(I, /obj/item/pda)) - var/obj/item/pda/pda = I - I = pda.id - if (I && istype(I)) - if(ACCESS_CAPTAIN in I.access) - if(security_level_cd > world.time) - to_chat(usr, "Security level protocols are currently on cooldown. Please stand by.") - return - var/old_level = GLOB.security_level - if(!tmp_alertlevel) - tmp_alertlevel = SEC_LEVEL_GREEN - if(tmp_alertlevel < SEC_LEVEL_GREEN) - tmp_alertlevel = SEC_LEVEL_GREEN - if(tmp_alertlevel > SEC_LEVEL_AMBER) - tmp_alertlevel = SEC_LEVEL_AMBER //Cannot engage delta with this - set_security_level(tmp_alertlevel) - security_level_cd = world.time + 15 SECONDS - if(GLOB.security_level != old_level) - to_chat(usr, "Authorization confirmed. Modifying security level.") - playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) - //Only notify people if an actual change happened - var/security_level = NUM2SECLEVEL(GLOB.security_level) - log_game("[key_name(usr)] has changed the security level to [security_level] with [src] at [AREACOORD(usr)].") - message_admins("[ADMIN_LOOKUPFLW(usr)] has changed the security level to [security_level] with [src] at [AREACOORD(usr)].") - deadchat_broadcast("[usr.real_name] has changed the security level to [security_level] with [src] at [get_area_name(usr, TRUE)].", usr) - tmp_alertlevel = 0 - else - to_chat(usr, "You are not authorized to do this!") - playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, FALSE) - tmp_alertlevel = 0 - state = STATE_DEFAULT - else - to_chat(usr, "You need to swipe your ID!") - playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, FALSE) - - if("announce") - if(authenticated==2) - playsound(src, 'sound/machines/terminal_prompt.ogg', 50, FALSE) - make_announcement(usr) - - if("crossserver") - if(authenticated==2) - var/dest = href_list["cross_dest"] - if(!checkCCcooldown()) - to_chat(usr, "Arrays recycling. Please stand by.") - playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, FALSE) - return - var/warning = dest == "all" ? "Please choose a message to transmit to allied stations." : "Please choose a message to transmit to [dest] sector station." - var/input = stripped_multiline_input(usr, "[warning] Please be aware that this process is very expensive, and abuse will lead to... termination.", "Send a message to an allied station.", "") - if(!input || !(usr in view(1,src)) || !checkCCcooldown()) - return - playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) - if(dest == "all") - send2otherserver("[station_name()]", input,"Comms_Console") - else - send2otherserver("[station_name()]", input,"Comms_Console", list(dest)) - minor_announce(input, title = "Outgoing message to allied station") - usr.log_talk(input, LOG_SAY, tag="message to the other server") - message_admins("[ADMIN_LOOKUPFLW(usr)] has sent a message to the other server.") - deadchat_broadcast("[usr.real_name] has sent an outgoing message to the other station(s).", usr) - CM.lastTimeUsed = world.time - - if("purchase_menu") - state = STATE_PURCHASE - - if("buyshuttle") - if(authenticated==2) - var/list/shuttles = flatten_list(SSmapping.shuttle_templates) - var/datum/map_template/shuttle/S = locate(href_list["chosen_shuttle"]) in shuttles - if(S && istype(S)) - if(SSshuttle.emergency.mode != SHUTTLE_RECALL && SSshuttle.emergency.mode != SHUTTLE_IDLE) - to_chat(usr, "It's a bit late to buy a new shuttle, don't you think?") - return - if(SSshuttle.shuttle_purchased) - to_chat(usr, "A replacement shuttle has already been purchased.") - else if(!S.prerequisites_met()) - to_chat(usr, "You have not met the requirements for purchasing this shuttle.") - else - var/points_to_check - var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR) - if(D) - points_to_check = D.account_balance - if(points_to_check >= S.credit_cost) - SSshuttle.shuttle_purchased = TRUE - SSshuttle.unload_preview() - SSshuttle.load_template(S) - SSshuttle.existing_shuttle = SSshuttle.emergency - SSshuttle.action_load(S) - D.adjust_money(-S.credit_cost) - minor_announce("[usr.real_name] has purchased [S.name] for [S.credit_cost] credits." , "Shuttle Purchase") - message_admins("[ADMIN_LOOKUPFLW(usr)] purchased [S.name].") - log_shuttle("[key_name(usr)] has purchased [S.name].") - SSblackbox.record_feedback("text", "shuttle_purchase", 1, "[S.name]") - else - to_chat(usr, "Insufficient credits.") - - if("callshuttle") - state = STATE_DEFAULT - if(authenticated && SSshuttle.canEvac(usr)) - state = STATE_CALLSHUTTLE - if("callshuttle2") - if(authenticated) - SSshuttle.requestEvac(usr, href_list["call"]) - if(SSshuttle.emergency.timer) - post_status("shuttle") - state = STATE_DEFAULT - if("cancelshuttle") - state = STATE_DEFAULT - if(authenticated) - state = STATE_CANCELSHUTTLE - if("cancelshuttle2") - if(authenticated) - if(SSshuttle.endvote_passed) //Citadel Edit - endvote passing = no recalls - say("Warning: Emergency shuttle recalls have been blocked by Central Command due to ongoing crew transfer procedures.") - else - SSshuttle.cancelEvac(usr) - state = STATE_DEFAULT - if("messagelist") - currmsg = 0 - state = STATE_MESSAGELIST - if("viewmessage") - state = STATE_VIEWMESSAGE - if (!currmsg) - if(href_list["message-num"]) - var/msgnum = text2num(href_list["message-num"]) - currmsg = messages[msgnum] - else - state = STATE_MESSAGELIST - if("delmessage") - state = currmsg ? STATE_DELMESSAGE : STATE_MESSAGELIST - if("delmessage2") - if(authenticated) - if(currmsg) - if(aicurrmsg == currmsg) - aicurrmsg = null - messages -= currmsg - currmsg = null - state = STATE_MESSAGELIST - else - state = STATE_VIEWMESSAGE - if("respond") - var/answer = text2num(href_list["answer"]) - if(!currmsg || !answer || currmsg.possible_answers.len < answer) - state = STATE_MESSAGELIST - else - if(!currmsg.answered) - currmsg.answered = answer - log_game("[key_name(usr)] answered [currmsg.title] comm message. Answer : [currmsg.answered]") - if(currmsg) - currmsg.answer_callback.InvokeAsync() - state = STATE_VIEWMESSAGE - updateDialog() - if("status") - state = STATE_STATUSDISPLAY - if("securitylevel") - tmp_alertlevel = text2num( href_list["newalertlevel"] ) - if(!tmp_alertlevel) - tmp_alertlevel = 0 - state = STATE_CONFIRM_LEVEL - if("changeseclevel") - state = STATE_ALERT_LEVEL - - if("emergencyaccess") - state = STATE_TOGGLE_EMERGENCY - if("enableemergency") - make_maint_all_access() - log_game("[key_name(usr)] enabled emergency maintenance access.") - message_admins("[ADMIN_LOOKUPFLW(usr)] enabled emergency maintenance access.") - deadchat_broadcast("[usr.real_name] enabled emergency maintenance access at [get_area_name(usr, TRUE)].", usr) - state = STATE_DEFAULT - if("disableemergency") - revoke_maint_all_access() - log_game("[key_name(usr)] disabled emergency maintenance access.") - message_admins("[ADMIN_LOOKUPFLW(usr)] disabled emergency maintenance access.") - deadchat_broadcast("[usr.real_name] disabled emergency maintenance access at [get_area_name(usr, TRUE)].", usr) - state = STATE_DEFAULT - - // Status display stuff - if("setstat") - playsound(src, "terminal_type", 50, FALSE) - switch(href_list["statdisp"]) - if("message") - post_status("message", stat_msg1, stat_msg2) - if("alert") - post_status("alert", href_list["alert"]) - else - post_status(href_list["statdisp"]) - - if("setmsg1") - stat_msg1 = reject_bad_text(input("Line 1", "Enter Message Text", stat_msg1) as text|null, 40) - updateDialog() - if("setmsg2") - stat_msg2 = reject_bad_text(input("Line 2", "Enter Message Text", stat_msg2) as text|null, 40) - updateDialog() - - // OMG CENTCOM LETTERHEAD - if("MessageCentCom") - if(authenticated) - if(!checkCCcooldown()) - to_chat(usr, "Arrays recycling. Please stand by.") - return - var/input = stripped_input(usr, "Please choose a message to transmit to CentCom via quantum entanglement. Please be aware that this process is very expensive, and abuse will lead to... termination. Transmission does not guarantee a response.", "Send a message to CentCom.", "") - if(!input || !(usr in view(1,src)) || !checkCCcooldown()) - return - playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) - CentCom_announce(input, usr) - to_chat(usr, "Message transmitted to Central Command.") - for(var/client/X in GLOB.admins) - if(X.prefs.toggles & SOUND_ADMINHELP) - SEND_SOUND(X, sound('sound/effects/printer.ogg')) - window_flash(X, ignorepref = FALSE) - usr.log_talk(input, LOG_SAY, tag="CentCom announcement") - deadchat_broadcast("[usr.real_name] has messaged CentCom, \"[input]\" at [get_area_name(usr, TRUE)].", usr) - CM.lastTimeUsed = world.time - - // OMG SYNDICATE ...LETTERHEAD - if("MessageSyndicate") - if((authenticated) && (obj_flags & EMAGGED)) - if(!checkCCcooldown()) - to_chat(usr, "Arrays recycling. Please stand by.") - playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, FALSE) - return - var/input = stripped_input(usr, "Please choose a message to transmit to \[ABNORMAL ROUTING COORDINATES\] via quantum entanglement. Please be aware that this process is very expensive, and abuse will lead to... termination. Transmission does not guarantee a response.", "Send a message to /??????/.", "") - if(!input || !(usr in view(1,src)) || !checkCCcooldown()) - return - playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) - Syndicate_announce(input, usr) - to_chat(usr, "SYSERR @l(19833)of(transmit.dm): !@$ MESSAGE TRANSMITTED TO SYNDICATE COMMAND.") - for(var/client/X in GLOB.admins) - if(X.prefs.toggles & SOUND_ADMINHELP) - SEND_SOUND(X, sound('sound/effects/printer.ogg')) - window_flash(X, ignorepref = FALSE) - usr.log_talk(input, LOG_SAY, tag="Syndicate announcement") - deadchat_broadcast("[usr.real_name] has messaged the Syndicate, \"[input]\" at [get_area_name(usr, TRUE)].", usr) - CM.lastTimeUsed = world.time - - if("RestoreBackup") - to_chat(usr, "Backup routing data restored!") - playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) - obj_flags &= ~EMAGGED - updateDialog() - - if("nukerequest") //When there's no other way - if(authenticated==2) - if(!checkCCcooldown()) - to_chat(usr, "Arrays recycling. Please stand by.") - return - var/input = stripped_input(usr, "Please enter the reason for requesting the nuclear self-destruct codes. Misuse of the nuclear request system will not be tolerated under any circumstances. Transmission does not guarantee a response.", "Self-Destruct Code Request.","") - if(!input || !(usr in view(1,src)) || !checkCCcooldown()) - return - Nuke_request(input, usr) - to_chat(usr, "Request sent.") - usr.log_message("has requested the nuclear codes from CentCom with reason \"[input]\"", LOG_SAY) - priority_announce("The codes for the on-station nuclear self-destruct have been requested by [usr]. Confirmation or denial of this request will be sent shortly.", "Nuclear Self Destruct Codes Requested","commandreport") - CM.lastTimeUsed = world.time - - - // AI interface - if("ai-main") - aicurrmsg = null - aistate = STATE_DEFAULT - if("ai-callshuttle") - aistate = STATE_DEFAULT - if(SSshuttle.canEvac(usr)) - aistate = STATE_CALLSHUTTLE - if("ai-callshuttle2") - SSshuttle.requestEvac(usr, href_list["call"]) - aistate = STATE_DEFAULT - if("ai-messagelist") - aicurrmsg = null - aistate = STATE_MESSAGELIST - if("ai-viewmessage") - aistate = STATE_VIEWMESSAGE - if (!aicurrmsg) - if(href_list["message-num"]) - var/msgnum = text2num(href_list["message-num"]) - aicurrmsg = messages[msgnum] - else - aistate = STATE_MESSAGELIST - if("ai-delmessage") - aistate = aicurrmsg ? STATE_DELMESSAGE : STATE_MESSAGELIST - if("ai-delmessage2") - if(aicurrmsg) - if(currmsg == aicurrmsg) - currmsg = null - messages -= aicurrmsg - aicurrmsg = null - aistate = STATE_MESSAGELIST - if("ai-respond") - var/answer = text2num(href_list["answer"]) - if(!aicurrmsg || !answer || aicurrmsg.possible_answers.len < answer) - aistate = STATE_MESSAGELIST - else - if(!aicurrmsg.answered) - aicurrmsg.answered = answer - log_game("[key_name(usr)] answered [aicurrmsg.title] comm message. Answer : [aicurrmsg.answered]") - if(aicurrmsg.answer_callback) - aicurrmsg.answer_callback.InvokeAsync() - aistate = STATE_VIEWMESSAGE - if("ai-status") - aistate = STATE_STATUSDISPLAY - if("ai-announce") - make_announcement(usr, 1) - if("ai-securitylevel") - if(security_level_cd > world.time) - to_chat(usr, "Security level protocols are currently on cooldown. Please stand by.") - return - tmp_alertlevel = text2num( href_list["newalertlevel"] ) - if(!tmp_alertlevel) - tmp_alertlevel = SEC_LEVEL_GREEN - var/old_level = GLOB.security_level - if(!tmp_alertlevel) - tmp_alertlevel = SEC_LEVEL_GREEN - if(tmp_alertlevel < SEC_LEVEL_GREEN) - tmp_alertlevel = SEC_LEVEL_GREEN - if(tmp_alertlevel > SEC_LEVEL_AMBER) - tmp_alertlevel = SEC_LEVEL_AMBER //Cannot engage delta with this - set_security_level(tmp_alertlevel) - security_level_cd = world.time + 15 SECONDS - if(GLOB.security_level != old_level) - //Only notify people if an actual change happened - var/security_level = NUM2SECLEVEL(GLOB.security_level) - log_game("[key_name(usr)] has changed the security level to [security_level] from [src] at [AREACOORD(usr)].") - message_admins("[ADMIN_LOOKUPFLW(usr)] has changed the security level to [security_level] from [src] at [AREACOORD(usr)].") - deadchat_broadcast("[usr.real_name] has changed the security level to [security_level] from [src] at [get_area_name(usr, TRUE)].", usr) - tmp_alertlevel = 0 - aistate = STATE_DEFAULT - if("ai-changeseclevel") - aistate = STATE_ALERT_LEVEL - if("ai-emergencyaccess") - aistate = STATE_TOGGLE_EMERGENCY - if("ai-enableemergency") - make_maint_all_access() - log_game("[key_name(usr)] enabled emergency maintenance access.") - message_admins("[ADMIN_LOOKUPFLW(usr)] enabled emergency maintenance access.") - deadchat_broadcast("[usr.real_name] enabled emergency maintenance access.", usr) - aistate = STATE_DEFAULT - if("ai-disableemergency") - revoke_maint_all_access() - log_game("[key_name(usr)] disabled emergency maintenance access.") - message_admins("[ADMIN_LOOKUPFLW(usr)] disabled emergency maintenance access.") - deadchat_broadcast("[usr.real_name] disabled emergency maintenance access.", usr) - aistate = STATE_DEFAULT - - updateUsrDialog() +/// Are we a silicon, OR logged in? +/obj/machinery/computer/communications/proc/authenticated(mob/user) + if (issilicon(user)) + return TRUE + return authenticated /obj/machinery/computer/communications/attackby(obj/I, mob/user, params) if(istype(I, /obj/item/card/id)) @@ -445,292 +71,413 @@ /obj/machinery/computer/communications/emag_act(mob/user) . = ..() - if(obj_flags & EMAGGED) + if (obj_flags & EMAGGED) return obj_flags |= EMAGGED - SSshuttle.shuttle_purchase_requirements_met |= "emagged" - if(authenticated == 1) - authenticated = 2 + if (authenticated) + authorize_access = get_all_accesses() to_chat(user, "You scramble the communication routing circuits!") playsound(src, 'sound/machines/terminal_alert.ogg', 50, FALSE) + return + +/obj/machinery/computer/communications/ui_act(action, list/params) + var/static/list/approved_states = list(STATE_BUYING_SHUTTLE, STATE_CHANGING_STATUS, STATE_MAIN, STATE_MESSAGES) + var/static/list/approved_status_pictures = list("biohazard", "blank", "default", "lockdown", "redalert", "shuttle") + + . = ..() + if (.) + return + + if (!has_communication()) + return + + . = TRUE + + switch (action) + if ("answerMessage") + if (!authenticated(usr)) + return + var/answer_index = text2num(params["answer"]) + var/message_index = text2num(params["message"]) + if (!answer_index || !message_index || answer_index < 1 || message_index < 1) + return + var/datum/comm_message/message = messages[message_index] + if (message.answered) + return + message.answered = answer_index + message.answer_callback.InvokeAsync() + if ("callShuttle") + if (!authenticated(usr) || !SSshuttle.canEvac(usr, TRUE)) + return + var/reason = trim(params["reason"], MAX_MESSAGE_LEN) + if (length(reason) < CALL_SHUTTLE_REASON_LENGTH) + return + SSshuttle.requestEvac(usr, reason) + post_status("shuttle") + if ("changeSecurityLevel") + if (!authenticated_as_silicon_or_captain(usr)) + return + + // Check if they have + if (!issilicon(usr)) + var/obj/item/held_item = usr.get_active_held_item() + var/obj/item/card/id/id_card = held_item?.GetID() + if (!istype(id_card)) + to_chat(usr, "You need to swipe your ID!") + playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, FALSE) + return + if (!(ACCESS_CAPTAIN in id_card.access)) + to_chat(usr, "You are not authorized to do this!") + playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, FALSE) + return + + var/new_sec_level = SECLEVEL2NUM(params["newSecurityLevel"]) + if (new_sec_level != SEC_LEVEL_GREEN && new_sec_level != SEC_LEVEL_BLUE && new_sec_level != SEC_LEVEL_AMBER) + return + if (GLOB.security_level == new_sec_level) + return + + set_security_level(new_sec_level) + + to_chat(usr, "Authorization confirmed. Modifying security level.") + playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) + + // Only notify people if an actual change happened + log_game("[key_name(usr)] has changed the security level to [params["newSecurityLevel"]] with [src] at [AREACOORD(usr)].") + message_admins("[ADMIN_LOOKUPFLW(usr)] has changed the security level to [params["newSecurityLevel"]] with [src] at [AREACOORD(usr)].") + deadchat_broadcast(" has changed the security level to [params["newSecurityLevel"]] with [src] at [get_area_name(usr, TRUE)].", "[usr.real_name]", usr) + + alert_level_tick += 1 + if ("deleteMessage") + if (!authenticated(usr)) + return + var/message_index = text2num(params["message"]) + if (!message_index) + return + LAZYREMOVE(messages, LAZYACCESS(messages, message_index)) + if ("makePriorityAnnouncement") + if (!authenticated_as_silicon_or_captain(usr)) + return + make_announcement(usr) + if ("messageAssociates") + if (!authenticated_as_non_silicon_captain(usr)) + return + if (!COOLDOWN_FINISHED(src, important_action_cooldown)) + return + + playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) + var/message = trim(html_encode(params["message"]), MAX_MESSAGE_LEN) + + var/emagged = obj_flags & EMAGGED + if (emagged) + message_syndicate(message, usr) + to_chat(usr, "SYSERR @l(19833)of(transmit.dm): !@$ MESSAGE TRANSMITTED TO SYNDICATE COMMAND.") + else + message_centcom(message, usr) + to_chat(usr, "Message transmitted to Central Command.") + + var/associates = emagged ? "the Syndicate": "CentCom" + usr.log_talk(message, LOG_SAY, tag = "message to [associates]") + deadchat_broadcast(" has messaged [associates], \"[message]\" at [get_area_name(usr, TRUE)].", "[usr.real_name]", usr) + COOLDOWN_START(src, important_action_cooldown, IMPORTANT_ACTION_COOLDOWN) + if ("purchaseShuttle") + var/can_buy_shuttles_or_fail_reason = can_buy_shuttles(usr) + if (can_buy_shuttles_or_fail_reason != TRUE) + if (can_buy_shuttles_or_fail_reason != FALSE) + to_chat(usr, "[can_buy_shuttles_or_fail_reason]") + return + var/list/shuttles = flatten_list(SSmapping.shuttle_templates) + var/datum/map_template/shuttle/shuttle = locate(params["shuttle"]) in shuttles + if (!istype(shuttle)) + return + if (!shuttle.prerequisites_met()) + to_chat(usr, "You have not met the requirements for purchasing this shuttle.") + return + var/datum/bank_account/bank_account = SSeconomy.get_dep_account(ACCOUNT_CAR) + if (bank_account.account_balance < shuttle.credit_cost) + return + SSshuttle.shuttle_purchased = SHUTTLEPURCHASE_PURCHASED + SSshuttle.unload_preview() + SSshuttle.existing_shuttle = SSshuttle.emergency + SSshuttle.action_load(shuttle, replace = TRUE) + bank_account.adjust_money(-shuttle.credit_cost) + minor_announce("[usr.real_name] has purchased [shuttle.name] for [shuttle.credit_cost] credits.[shuttle.extra_desc ? " [shuttle.extra_desc]" : ""]" , "Shuttle Purchase") + message_admins("[ADMIN_LOOKUPFLW(usr)] purchased [shuttle.name].") + log_shuttle("[key_name(usr)] has purchased [shuttle.name].") + SSblackbox.record_feedback("text", "shuttle_purchase", 1, shuttle.name) + state = STATE_MAIN + if ("recallShuttle") + // AIs cannot recall the shuttle + if (!authenticated(usr) || issilicon(usr)) + return + SSshuttle.cancelEvac(usr) + if ("requestNukeCodes") + if (!authenticated_as_non_silicon_captain(usr)) + return + if (!COOLDOWN_FINISHED(src, important_action_cooldown)) + return + var/reason = trim(html_encode(params["reason"]), MAX_MESSAGE_LEN) + nuke_request(reason, usr) + to_chat(usr, "Request sent.") + usr.log_message("has requested the nuclear codes from CentCom with reason \"[reason]\"", LOG_SAY) + priority_announce("The codes for the on-station nuclear self-destruct have been requested by [usr]. Confirmation or denial of this request will be sent shortly.", "Nuclear Self-Destruct Codes Requested", "commandreport") + playsound(src, 'sound/machines/terminal_prompt.ogg', 50, FALSE) + COOLDOWN_START(src, important_action_cooldown, IMPORTANT_ACTION_COOLDOWN) + if ("restoreBackupRoutingData") + if (!authenticated_as_non_silicon_captain(usr)) + return + if (!(obj_flags & EMAGGED)) + return + to_chat(usr, "Backup routing data restored.") + playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) + obj_flags &= ~EMAGGED + if ("sendToOtherSector") + if (!authenticated_as_non_silicon_captain(usr)) + return + if (!can_send_messages_to_other_sectors(usr)) + return + if (!COOLDOWN_FINISHED(src, important_action_cooldown)) + return + + var/message = trim(html_encode(params["message"]), MAX_MESSAGE_LEN) + if (!message) + return + + playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) + + var/destination = params["destination"] + var/list/payload = list() + + var/network_name = CONFIG_GET(string/cross_comms_network) + if (network_name) + payload["network"] = network_name + + send2otherserver(station_name(), message, "Comms_Console", destination == "all" ? null : list(destination), additional_data = payload) + minor_announce(message, title = "Outgoing message to allied station") + usr.log_talk(message, LOG_SAY, tag = "message to the other server") + message_admins("[ADMIN_LOOKUPFLW(usr)] has sent a message to the other server\[s].") + deadchat_broadcast(" has sent an outgoing message to the other station(s).", "[usr.real_name]", usr) + + COOLDOWN_START(src, important_action_cooldown, IMPORTANT_ACTION_COOLDOWN) + if ("setState") + if (!authenticated(usr)) + return + if (!(params["state"] in approved_states)) + return + if (state == STATE_BUYING_SHUTTLE && can_buy_shuttles(usr) != TRUE) + return + set_state(usr, params["state"]) + playsound(src, "terminal_type", 50, FALSE) + if ("setStatusMessage") + if (!authenticated(usr)) + return + var/line_one = reject_bad_text(params["lineOne"] || "", MAX_STATUS_LINE_LENGTH) + var/line_two = reject_bad_text(params["lineTwo"] || "", MAX_STATUS_LINE_LENGTH) + post_status("alert", "blank") + post_status("message", line_one, line_two) + last_status_display = list(line_one, line_two) + playsound(src, "terminal_type", 50, FALSE) + if ("setStatusPicture") + if (!authenticated(usr)) + return + var/picture = params["picture"] + if (!(picture in approved_status_pictures)) + return + post_status("alert", picture) + playsound(src, "terminal_type", 50, FALSE) + if ("toggleAuthentication") + // Log out if we're logged in + if (authorize_name) + authenticated = FALSE + authorize_access = null + authorize_name = null + playsound(src, 'sound/machines/terminal_off.ogg', 50, FALSE) + return + + if (obj_flags & EMAGGED) + authenticated = TRUE + authorize_access = get_all_accesses() + authorize_name = "Unknown" + to_chat(usr, "[src] lets out a quiet alarm as its login is overridden.") + playsound(src, 'sound/machines/terminal_alert.ogg', 25, FALSE) + else if(isliving(usr)) + var/mob/living/L = usr + var/obj/item/card/id/id_card = L.get_idcard(hand_first = TRUE) + if (check_access(id_card)) + authenticated = TRUE + authorize_access = id_card.access + authorize_name = "[id_card.registered_name] - [id_card.assignment]" + + state = STATE_MAIN + playsound(src, 'sound/machines/terminal_on.ogg', 50, FALSE) + if ("toggleEmergencyAccess") + if (!authenticated_as_silicon_or_captain(usr)) + return + if (GLOB.emergency_access) + revoke_maint_all_access() + log_game("[key_name(usr)] disabled emergency maintenance access.") + message_admins("[ADMIN_LOOKUPFLW(usr)] disabled emergency maintenance access.") + deadchat_broadcast(" disabled emergency maintenance access at [get_area_name(usr, TRUE)].", "[usr.real_name]", usr) + else + make_maint_all_access() + log_game("[key_name(usr)] enabled emergency maintenance access.") + message_admins("[ADMIN_LOOKUPFLW(usr)] enabled emergency maintenance access.") + deadchat_broadcast(" enabled emergency maintenance access at [get_area_name(usr, TRUE)].", "[usr.real_name]", usr) + +/obj/machinery/computer/communications/ui_data(mob/user) + var/list/data = list( + "authenticated" = FALSE, + "emagged" = FALSE, + "hasConnection" = has_communication(), + ) + + var/ui_state = issilicon(user) ? cyborg_state : state + + if (authenticated || issilicon(user)) + data["authenticated"] = TRUE + data["canLogOut"] = !issilicon(user) + data["page"] = ui_state + + if (obj_flags & EMAGGED) + data["emagged"] = TRUE + + switch (ui_state) + if (STATE_MAIN) + data["canBuyShuttles"] = can_buy_shuttles(user) + data["canMakeAnnouncement"] = FALSE + data["canMessageAssociates"] = FALSE + data["canRecallShuttles"] = !issilicon(user) + data["canRequestNuke"] = FALSE + data["canSendToSectors"] = FALSE + data["canSetAlertLevel"] = FALSE + data["canToggleEmergencyAccess"] = FALSE + data["importantActionReady"] = COOLDOWN_FINISHED(src, important_action_cooldown) + data["shuttleCalled"] = FALSE + data["shuttleLastCalled"] = FALSE + + data["alertLevel"] = NUM2SECLEVEL(GLOB.security_level) + data["authorizeName"] = authorize_name + data["canLogOut"] = !issilicon(user) + data["shuttleCanEvacOrFailReason"] = SSshuttle.canEvac(user, TRUE) + + if (authenticated_as_non_silicon_captain(user)) + data["canMessageAssociates"] = TRUE + data["canRequestNuke"] = TRUE + + if (can_send_messages_to_other_sectors(user)) + data["canSendToSectors"] = TRUE + + var/list/sectors = list() + var/our_id = CONFIG_GET(string/cross_comms_name) + + for (var/server in CONFIG_GET(keyed_list/cross_server)) + if (server == our_id) + continue + sectors += server + + data["sectors"] = sectors + + if (authenticated_as_silicon_or_captain(user)) + data["canToggleEmergencyAccess"] = TRUE + data["emergencyAccess"] = GLOB.emergency_access + + data["alertLevelTick"] = alert_level_tick + data["canMakeAnnouncement"] = TRUE + data["canSetAlertLevel"] = issilicon(user) ? "NO_SWIPE_NEEDED" : "SWIPE_NEEDED" + + if (SSshuttle.emergency.mode != SHUTTLE_IDLE && SSshuttle.emergency.mode != SHUTTLE_RECALL) + data["shuttleCalled"] = TRUE + data["shuttleRecallable"] = SSshuttle.canRecall() + + if (SSshuttle.emergencyCallAmount) + data["shuttleCalledPreviously"] = TRUE + if (SSshuttle.emergencyLastCallLoc) + data["shuttleLastCalled"] = format_text(SSshuttle.emergencyLastCallLoc.name) + if (STATE_MESSAGES) + data["messages"] = list() + + if (messages) + for (var/_message in messages) + var/datum/comm_message/message = _message + data["messages"] += list(list( + "answered" = message.answered, + "content" = message.content, + "title" = message.title, + "possibleAnswers" = message.possible_answers, + )) + if (STATE_BUYING_SHUTTLE) + var/datum/bank_account/bank_account = SSeconomy.get_dep_account(ACCOUNT_CAR) + var/list/shuttles = list() + + for (var/shuttle_id in SSmapping.shuttle_templates) + var/datum/map_template/shuttle/shuttle_template = SSmapping.shuttle_templates[shuttle_id] + if (!shuttle_template.can_be_bought || shuttle_template.credit_cost == INFINITY) + continue + shuttles += list(list( + "name" = shuttle_template.name, + "description" = shuttle_template.description, + "creditCost" = shuttle_template.credit_cost, + "prerequisites" = shuttle_template.prerequisites, + "ref" = REF(shuttle_template), + )) + + data["budget"] = bank_account.account_balance + data["shuttles"] = shuttles + if (STATE_CHANGING_STATUS) + data["lineOne"] = last_status_display ? last_status_display[1] : "" + data["lineTwo"] = last_status_display ? last_status_display[2] : "" + + return data + +/obj/machinery/computer/communications/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if (!ui) + ui = new(user, src, "CommunicationsConsole") + ui.open() + +/obj/machinery/computer/communications/ui_static_data(mob/user) + return list( + "callShuttleReasonMinLength" = CALL_SHUTTLE_REASON_LENGTH, + "maxStatusLineLength" = MAX_STATUS_LINE_LENGTH, + "maxMessageLength" = MAX_MESSAGE_LEN, + ) + +/// Returns whether or not the communications console can communicate with the station +/obj/machinery/computer/communications/proc/has_communication() + var/turf/current_turf = get_turf(src) + var/z_level = current_turf.z + return is_station_level(z_level) || is_centcom_level(z_level) + +/obj/machinery/computer/communications/proc/set_state(mob/user, new_state) + if (issilicon(user)) + cyborg_state = new_state + else + state = new_state + +/// Returns TRUE if the user can buy shuttles. +/// If they cannot, returns FALSE or a string detailing why. +/obj/machinery/computer/communications/proc/can_buy_shuttles(mob/user) + if (!SSmapping.config.allow_custom_shuttles) + return FALSE + if (!authenticated_as_non_silicon_captain(user)) + return FALSE + if (SSshuttle.emergency.mode != SHUTTLE_RECALL && SSshuttle.emergency.mode != SHUTTLE_IDLE) + return "The shuttle is already in transit." + if (SSshuttle.shuttle_purchased == SHUTTLEPURCHASE_PURCHASED) + return "A replacement shuttle has already been purchased." + if (SSshuttle.shuttle_purchased == SHUTTLEPURCHASE_FORCED) + return "Due to unforseen circumstances, shuttle purchasing is no longer available." return TRUE -/obj/machinery/computer/communications/ui_interact(mob/user) - . = ..() - if (z > 6) - to_chat(user, "Unable to establish a connection: \black You're too far away from the station!") +/obj/machinery/computer/communications/proc/can_send_messages_to_other_sectors(mob/user) + if (!authenticated_as_non_silicon_captain(user)) return - var/dat = "" - if(SSshuttle.emergency.mode == SHUTTLE_CALL) - var/timeleft = SSshuttle.emergency.timeLeft() - dat += "Emergency shuttle\n
\nETA: [timeleft / 60 % 60]:[add_leading(num2text(timeleft % 60), 2, "0")]" + return length(CONFIG_GET(keyed_list/cross_server)) > 0 - - var/datum/browser/popup = new(user, "communications", "Communications Console", 400, 500) - - if(issilicon(user)) - var/dat2 = interact_ai(user) // give the AI a different interact proc to limit its access - if(dat2) - dat += dat2 - popup.set_content(dat) - popup.open() - return - - switch(state) - if(STATE_DEFAULT) - if (authenticated) - if(SSshuttle.emergencyCallAmount) - if(SSshuttle.emergencyLastCallLoc) - dat += "Most recent shuttle call/recall traced to: [format_text(SSshuttle.emergencyLastCallLoc.name)]
" - else - dat += "Unable to trace most recent shuttle call/recall signal.
" - dat += "Logged in as: [auth_id]" - dat += "
" - dat += "
\[ Log Out \]
" - dat += "
General Functions" - dat += "
\[ Message List \]" - switch(SSshuttle.emergency.mode) - if(SHUTTLE_IDLE, SHUTTLE_RECALL) - dat += "
\[ Call Emergency Shuttle \]" - else - dat += "
\[ Cancel Shuttle Call \]" - - dat += "
\[ Set Status Display \]" - if (authenticated==2) - dat += "

Captain Functions" - dat += "
\[ Make a Captain's Announcement \]" - var/list/cross_servers = CONFIG_GET(keyed_list/cross_server) - var/our_id = CONFIG_GET(string/cross_comms_name) - if(cross_servers.len) - for(var/server in cross_servers) - if(server == our_id) - continue - dat += "
\[ Send a message to station in [server] sector. \]" - if(cross_servers.len > 2) - dat += "
\[ Send a message to all allied stations \]" - if(SSmapping.config.allow_custom_shuttles) - dat += "
\[ Purchase Shuttle \]" - dat += "
\[ Change Alert Level \]" - dat += "
\[ Emergency Maintenance Access \]" - dat += "
\[ Request Nuclear Authentication Codes \]" - if(!(obj_flags & EMAGGED)) - dat += "
\[ Send Message to CentCom \]" - else - dat += "
\[ Send Message to \[UNKNOWN\] \]" - dat += "
\[ Restore Backup Routing Data \]" - else - dat += "
\[ Log In \]" - if(STATE_CALLSHUTTLE) - dat += get_call_shuttle_form() - playsound(src, 'sound/machines/terminal_prompt.ogg', 50, FALSE) - if(STATE_CANCELSHUTTLE) - dat += get_cancel_shuttle_form() - playsound(src, 'sound/machines/terminal_prompt.ogg', 50, FALSE) - if(STATE_MESSAGELIST) - dat += "Messages:" - for(var/i in 1 to messages.len) - var/datum/comm_message/M = messages[i] - dat += "
[M.title]" - playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) - if(STATE_VIEWMESSAGE) - if (currmsg) - dat += "[currmsg.title]

[currmsg.content]" - if(!currmsg.answered && currmsg.possible_answers.len) - for(var/i in 1 to currmsg.possible_answers.len) - var/answer = currmsg.possible_answers[i] - dat += "
\[ Answer : [answer] \]" - else if(currmsg.answered) - var/answered = currmsg.possible_answers[currmsg.answered] - dat += "
Archived Answer : [answered]" - dat += "

\[ Delete \]" - else - aistate = STATE_MESSAGELIST - attack_hand(user) - return - if(STATE_DELMESSAGE) - if (currmsg) - dat += "Are you sure you want to delete this message? \[ OK | Cancel \]" - else - state = STATE_MESSAGELIST - attack_hand(user) - return - if(STATE_STATUSDISPLAY) - dat += "Set Status Displays
" - dat += "\[ Clear \]
" - dat += "\[ Shuttle ETA \]
" - dat += "\[ Message \]" - dat += "
" - dat += "\[ Alert: None |" - dat += " Red Alert |" - dat += " Lockdown |" - dat += " Biohazard \]

" - playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) - if(STATE_ALERT_LEVEL) - dat += "Current alert level: [NUM2SECLEVEL(GLOB.security_level)]
" - if(GLOB.security_level == SEC_LEVEL_DELTA) - dat += "The self-destruct mechanism is active. Find a way to deactivate the mechanism to lower the alert level or evacuate." - else - dat += "Amber
" - dat += "Blue
" - dat += "Green" - if(STATE_CONFIRM_LEVEL) - dat += "Current alert level: [NUM2SECLEVEL(GLOB.security_level)]
" - dat += "Confirm the change to: [NUM2SECLEVEL(tmp_alertlevel)]
" - dat += "Swipe ID to confirm change.
" - if(STATE_TOGGLE_EMERGENCY) - playsound(src, 'sound/machines/terminal_prompt.ogg', 50, FALSE) - if(GLOB.emergency_access == 1) - dat += "Emergency Maintenance Access is currently ENABLED" - dat += "
Restore maintenance access restrictions?
\[ OK | Cancel \]" - else - dat += "Emergency Maintenance Access is currently DISABLED" - dat += "
Lift access restrictions on maintenance and external airlocks?
\[ OK | Cancel \]" - - if(STATE_PURCHASE) - var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR) - dat += "Budget: [D.account_balance] Credits.
" - dat += "
" - dat += "Caution: Purchasing dangerous shuttles may lead to mutiny and/or death.
" - dat += "
" - for(var/shuttle_id in SSmapping.shuttle_templates) - var/datum/map_template/shuttle/S = SSmapping.shuttle_templates[shuttle_id] - if(S.can_be_bought && S.credit_cost < INFINITY) - dat += "[S.name] | [S.credit_cost] Credits
" - dat += "[S.description]
" - if(S.prerequisites) - dat += "Prerequisites: [S.prerequisites]
" - dat += "(Purchase)

" - - dat += "

\[ [(state != STATE_DEFAULT) ? "Main Menu | " : ""]Close \]" - - popup.set_content(dat) - popup.open() - -/obj/machinery/computer/communications/proc/get_javascript_header(form_id) - var/dat = {""} - return dat - -/obj/machinery/computer/communications/proc/get_call_shuttle_form(ai_interface = 0) - var/form_id = "callshuttle" - var/dat = get_javascript_header(form_id) - dat += "
" - dat += "" - dat += "" - dat += "Nature of emergency:
" - dat += "
Are you sure you want to call the shuttle? \[ Call \]" - return dat - -/obj/machinery/computer/communications/proc/get_cancel_shuttle_form() - var/form_id = "cancelshuttle" - var/dat = get_javascript_header(form_id) - dat += "" - dat += "" - dat += "" - - dat += "
Are you sure you want to cancel the shuttle? \[ Cancel \]" - return dat - -/obj/machinery/computer/communications/proc/interact_ai(mob/living/silicon/ai/user) - var/dat = "" - switch(aistate) - if(STATE_DEFAULT) - if(SSshuttle.emergencyCallAmount) - if(SSshuttle.emergencyLastCallLoc) - dat += "Latest emergency signal trace attempt successful.
Last signal origin: [format_text(SSshuttle.emergencyLastCallLoc.name)].
" - else - dat += "Latest emergency signal trace attempt failed.
" - if(authenticated) - dat += "Current login: [auth_id]" - else - dat += "Current login: None" - dat += "

General Functions" - dat += "
\[ Message List \]" - if(SSshuttle.emergency.mode == SHUTTLE_IDLE) - dat += "
\[ Call Emergency Shuttle \]" - dat += "
\[ Set Status Display \]" - dat += "

Special Functions" - dat += "
\[ Make an Announcement \]" - dat += "
\[ Change Alert Level \]" - dat += "
\[ Emergency Maintenance Access \]" - if(STATE_CALLSHUTTLE) - dat += get_call_shuttle_form(1) - if(STATE_MESSAGELIST) - dat += "Messages:" - for(var/i in 1 to messages.len) - var/datum/comm_message/M = messages[i] - dat += "
[M.title]" - if(STATE_VIEWMESSAGE) - if (aicurrmsg) - dat += "[aicurrmsg.title]

[aicurrmsg.content]" - if(!aicurrmsg.answered && aicurrmsg.possible_answers.len) - for(var/i in 1 to aicurrmsg.possible_answers.len) - var/answer = aicurrmsg.possible_answers[i] - dat += "
\[ Answer : [answer] \]" - else if(aicurrmsg.answered) - var/answered = aicurrmsg.possible_answers[aicurrmsg.answered] - dat += "
Archived Answer : [answered]" - dat += "

\[ Delete \]" - else - aistate = STATE_MESSAGELIST - attack_hand(user) - return null - if(STATE_DELMESSAGE) - if(aicurrmsg) - dat += "Are you sure you want to delete this message? \[ OK | Cancel \]" - else - aistate = STATE_MESSAGELIST - attack_hand(user) - return - - if(STATE_STATUSDISPLAY) - dat += "Set Status Displays
" - dat += "\[ Clear \]
" - dat += "\[ Shuttle ETA \]
" - dat += "\[ Message \]" - dat += "
" - dat += "\[ Alert: None |" - dat += " Red Alert |" - dat += " Lockdown |" - dat += " Biohazard \]

" - - if(STATE_ALERT_LEVEL) - dat += "Current alert level: [NUM2SECLEVEL(GLOB.security_level)]
" - if(GLOB.security_level == SEC_LEVEL_DELTA) - dat += "The self-destruct mechanism is active. Find a way to deactivate the mechanism to lower the alert level or evacuate." - else - dat += "Amber
" - dat += "Blue
" - dat += "Green" - - if(STATE_TOGGLE_EMERGENCY) - if(GLOB.emergency_access == 1) - dat += "Emergency Maintenance Access is currently ENABLED" - dat += "
Restore maintenance access restrictions?
\[ OK | Cancel \]" - else - dat += "Emergency Maintenance Access is currently DISABLED" - dat += "
Lift access restrictions on maintenance and external airlocks?
\[ OK | Cancel \]" - - dat += "

\[ [(aistate != STATE_DEFAULT) ? "Main Menu | " : ""]Close \]" - return dat - -/obj/machinery/computer/communications/proc/make_announcement(mob/living/user, is_silicon) - if(!SScommunications.can_announce(user, is_silicon)) +/obj/machinery/computer/communications/proc/make_announcement(mob/living/user) + var/is_ai = issilicon(user) + if(!SScommunications.can_announce(user, is_ai)) to_chat(user, "Intercomms recharging. Please stand by.") return var/input = stripped_input(user, "Please choose a message to announce to the station crew.", "What?") @@ -741,8 +488,8 @@ to_chat(user, "You find yourself unable to speak.") else input = user.treat_message(input) //Adds slurs and so on. Someone should make this use languages too. - SScommunications.make_announcement(user, is_silicon, input) - deadchat_broadcast("[user.real_name] made an priority announcement from [get_area_name(usr, TRUE)].", user) + SScommunications.make_announcement(user, is_ai, input) + deadchat_broadcast(" made a priority announcement from [get_area_name(usr, TRUE)].", "[user.real_name]", user) /obj/machinery/computer/communications/proc/post_status(command, data1, data2) @@ -761,18 +508,18 @@ frequency.post_signal(src, status_signal) - /obj/machinery/computer/communications/Destroy() GLOB.shuttle_caller_list -= src SSshuttle.autoEvac() return ..() -/obj/machinery/computer/communications/proc/overrideCooldown() - var/obj/item/circuitboard/computer/communications/CM = circuit - CM.lastTimeUsed = 0 +/// Override the cooldown for special actions +/// Used in places such as CentCom messaging back so that the crew can answer right away +/obj/machinery/computer/communications/proc/override_cooldown() + COOLDOWN_RESET(src, important_action_cooldown) /obj/machinery/computer/communications/proc/add_message(datum/comm_message/new_message) - messages += new_message + LAZYADD(messages, new_message) /datum/comm_message var/title @@ -790,14 +537,9 @@ if(new_possible_answers) possible_answers = new_possible_answers -#undef STATE_DEFAULT -#undef STATE_CALLSHUTTLE -#undef STATE_CANCELSHUTTLE -#undef STATE_MESSAGELIST -#undef STATE_VIEWMESSAGE -#undef STATE_DELMESSAGE -#undef STATE_STATUSDISPLAY -#undef STATE_ALERT_LEVEL -#undef STATE_CONFIRM_LEVEL -#undef STATE_TOGGLE_EMERGENCY -#undef STATE_PURCHASE +#undef IMPORTANT_ACTION_COOLDOWN +#undef MAX_STATUS_LINE_LENGTH +#undef STATE_BUYING_SHUTTLE +#undef STATE_CHANGING_STATUS +#undef STATE_MAIN +#undef STATE_MESSAGES diff --git a/code/game/machinery/computer/pod.dm b/code/game/machinery/computer/pod.dm index ca64d538b9..97e2d4ea0a 100644 --- a/code/game/machinery/computer/pod.dm +++ b/code/game/machinery/computer/pod.dm @@ -1,21 +1,38 @@ /obj/machinery/computer/pod name = "mass driver launch control" desc = "A combined blastdoor and mass driver control unit." + // processing_flags = START_PROCESSING_MANUALLY + /// Connected mass driver var/obj/machinery/mass_driver/connected = null - var/title = "Mass Driver Controls" + /// ID of the launch control var/id = 1 - var/timing = 0 + /// If the launch timer counts down + var/timing = FALSE + /// Time before auto launch var/time = 30 + /// Range in which we search for a mass drivers and poddoors nearby var/range = 4 - + /// Countdown timer for the mass driver's delayed launch functionality. + COOLDOWN_DECLARE(massdriver_countdown) /obj/machinery/computer/pod/Initialize() . = ..() for(var/obj/machinery/mass_driver/M in range(range, src)) if(M.id == id) connected = M + break +/obj/machinery/computer/pod/process(delta_time) + if(COOLDOWN_FINISHED(src, massdriver_countdown)) + timing = FALSE + // alarm() sleeps, so we want to end processing first and can't rely on return PROCESS_KILL + // end_processing() + STOP_PROCESSING(SSmachines, src) + alarm() +/** + * Initiates launching sequence by checking if all components are functional, opening poddoors, firing mass drivers and then closing poddoors + */ /obj/machinery/computer/pod/proc/alarm() if(stat & (NOPOWER|BROKEN)) return @@ -39,92 +56,112 @@ if(M.id == id) M.close() -/obj/machinery/computer/pod/ui_interact(mob/user) +/obj/machinery/computer/pod/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "MassDriverControl", name) + ui.open() + +/obj/machinery/computer/pod/ui_data(mob/user) + var/list/data = list() + // If the cooldown has finished, just display the time. If the cooldown hasn't finished, display the cooldown. + var/display_time = COOLDOWN_FINISHED(src, massdriver_countdown) ? time : COOLDOWN_TIMELEFT(src, massdriver_countdown) * 0.1 + data["connected"] = connected ? TRUE : FALSE + data["seconds"] = round(display_time % 60) + data["minutes"] = round((display_time - data["seconds"]) / 60) + data["timing"] = timing + data["power"] = connected ? connected.power : 0.25 + data["poddoor"] = FALSE + for(var/obj/machinery/door/poddoor/door in range(range, src)) + if(door.id == id) + data["poddoor"] = TRUE + break + return data + +/obj/machinery/computer/pod/ui_act(action, list/params) . = ..() - if(!allowed(user)) - to_chat(user, "Access denied.") + if(.) + return + if(!allowed(usr)) + to_chat(usr, "Access denied.") return - var/dat = "" - if(connected) - var/d2 - if(timing) //door controls do not need timers. - d2 = "Stop Time Launch" - else - d2 = "Initiate Time Launch" - dat += "
\nTimer System: [d2]\nTime Left: [DisplayTimeText(time)] - - + +" - var/temp = "" - var/list/L = list( 0.25, 0.5, 1, 2, 4, 8, 16 ) - for(var/t in L) - if(t == connected.power) - temp += "[t] " + switch(action) + if("set_power") + if(!connected) + return + var/value = text2num(params["power"]) + if(!value) + return + value = clamp(value, 0.25, 16) + connected.power = value + return TRUE + if("launch") + alarm() + return TRUE + if("time") + timing = !timing + if(timing) + COOLDOWN_START(src, massdriver_countdown, time SECONDS) + // begin_processing() + START_PROCESSING(SSmachines, src) else - temp += "[t] " - dat += "
\nPower Level: [temp]
\nFiring Sequence
\nTest Fire Driver
\nToggle Outer Door
" - else - dat += "
\nToggle Outer Door
" - dat += "

Close" - add_fingerprint(usr) - var/datum/browser/popup = new(user, "computer", title, 400, 500) - popup.set_content(dat) - popup.open() - -/obj/machinery/computer/pod/process() - if(!..()) - return - if(timing) - if(time > 0) - time = round(time) - 1 - else - alarm() - time = 0 - timing = 0 - updateDialog() - - -/obj/machinery/computer/pod/Topic(href, href_list) - if(..()) - return - if(usr.contents.Find(src) || (in_range(src, usr) && isturf(loc)) || hasSiliconAccessInArea(usr)) - usr.set_machine(src) - if(href_list["power"]) - var/t = text2num(href_list["power"]) - t = min(max(0.25, t), 16) - if(connected) - connected.power = t - if(href_list["alarm"]) - alarm() - if(href_list["time"]) - timing = text2num(href_list["time"]) - if(href_list["tp"]) - var/tp = text2num(href_list["tp"]) - time += tp - time = min(max(round(time), 0), 120) - if(href_list["door"]) + time = COOLDOWN_TIMELEFT(src, massdriver_countdown) * 0.1 + COOLDOWN_RESET(src, massdriver_countdown) + // end_processing() + STOP_PROCESSING(SSmachines, src) + return TRUE + if("input") + var/value = text2num(params["adjust"]) + if(!value) + return + value = round(time + value) + time = clamp(value, 0, 120) + return TRUE + if("door") for(var/obj/machinery/door/poddoor/M in range(range, src)) if(M.id == id) if(M.density) M.open() else M.close() - if(href_list["drive"]) + return TRUE + if("driver_test") for(var/obj/machinery/mass_driver/M in range(range, src)) if(M.id == id) - M.power = connected.power + M.power = connected?.power M.drive() - updateUsrDialog() + return TRUE /obj/machinery/computer/pod/old name = "\improper DoorMex control console" - title = "Door Controls" icon_state = "oldcomp" icon_screen = "library" - icon_keyboard = null + icon_keyboard = "no_keyboard" + +// /obj/machinery/computer/pod/old/mass_driver_controller +// name = "\improper Mass Driver Controller" +// icon = 'icons/obj/airlock_machines.dmi' +// icon_state = "airlock_control_standby" +// icon_keyboard = "no_keyboard" +// density = FALSE + +// /obj/machinery/computer/pod/old/mass_driver_controller/toxinsdriver +// id = MASSDRIVER_TOXINS + +// //for maps where pod doors are outside of the standard 4 tile controller detection range (ie Pubbystation) +// /obj/machinery/computer/pod/old/mass_driver_controller/toxinsdriver/longrange +// range = 6 + +// /obj/machinery/computer/pod/old/mass_driver_controller/chapelgun +// id = MASSDRIVER_CHAPEL + +// /obj/machinery/computer/pod/old/mass_driver_controller/trash +// id = MASSDRIVER_DISPOSALS /obj/machinery/computer/pod/old/syndicate name = "\improper ProComp Executive IIc" desc = "The Syndicate operate on a tight budget. Operates external airlocks." - title = "External Airlock Controls" req_access = list(ACCESS_SYNDICATE) /obj/machinery/computer/pod/old/swf diff --git a/code/game/machinery/mass_driver.dm b/code/game/machinery/mass_driver.dm index b39c6d350f..8b9333b5e0 100644 --- a/code/game/machinery/mass_driver.dm +++ b/code/game/machinery/mass_driver.dm @@ -11,8 +11,24 @@ var/id = 1 var/drive_range = 50 //this is mostly irrelevant since current mass drivers throw into space, but you could make a lower-range mass driver for interstation transport or something I guess. -/obj/machinery/mass_driver/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE) - id = "[idnum][id]" +// /obj/machinery/mass_driver/chapelgun +// name = "holy driver" +// id = MASSDRIVER_CHAPEL + +// /obj/machinery/mass_driver/toxins +// id = MASSDRIVER_TOXINS + +// /obj/machinery/mass_driver/trash +// id = MASSDRIVER_DISPOSALS + +// /obj/machinery/mass_driver/Destroy() +// for(var/obj/machinery/computer/pod/control in GLOB.machines) +// if(control.id == id) +// control.connected = null +// return ..() + +/obj/machinery/mass_driver/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock) + id = "[port.id]_[id]" /obj/machinery/mass_driver/proc/drive(amount) if(stat & (BROKEN|NOPOWER)) @@ -22,6 +38,8 @@ var/atom/target = get_edge_target_turf(src, dir) for(var/atom/movable/O in loc) if(!O.anchored || ismecha(O)) //Mechs need their launch platforms. + if(ismob(O) && !isliving(O)) + continue O_limit++ if(O_limit >= 20) audible_message("[src] lets out a screech, it doesn't seem to be able to handle the load.") @@ -30,7 +48,6 @@ O.throw_at(target, drive_range * power, power) flick("mass_driver1", src) - /obj/machinery/mass_driver/emp_act(severity) . = ..() if (. & EMP_PROTECT_SELF) diff --git a/code/game/machinery/telecomms/machine_interactions.dm b/code/game/machinery/telecomms/machine_interactions.dm index d1c7222485..9876206724 100644 --- a/code/game/machinery/telecomms/machine_interactions.dm +++ b/code/game/machinery/telecomms/machine_interactions.dm @@ -243,13 +243,14 @@ // Check if the user can use it. /obj/machinery/telecomms/proc/canInteract(mob/user) - var/get = user.get_active_held_item() - var/obj/item/I = get - if(I.tool_behaviour == TOOL_MULTITOOL) - return TRUE + var/obj/item/I = user.get_active_held_item() + if(!issilicon(user) && I) + if(I.tool_behaviour == TOOL_MULTITOOL) + return TRUE if(hasSiliconAccessInArea(user)) return TRUE return FALSE + // Check if the user is nearby and has a multitool. /obj/machinery/telecomms/proc/canAccess(mob/user) if((canInteract(user) && in_range(user, src)) || hasSiliconAccessInArea(user)) @@ -262,14 +263,13 @@ return null var/obj/item/P = user.get_active_held_item() // Is the ref not a null? and is it the actual type? - if(P.tool_behaviour == TOOL_MULTITOOL) - return P - else if(isAI(user)) + if(isAI(user)) var/mob/living/silicon/ai/U = user P = U.aiMulti - else if(iscyborg(user) && in_range(user, src)) - var/get = user.get_active_held_item() - var/obj/item/I = get - if(I.tool_behaviour == TOOL_MULTITOOL) - I = user.get_active_held_item() + else if(iscyborg(user) && !in_range(user, src)) + return null + if(!P) + return null + else if(P.tool_behaviour == TOOL_MULTITOOL) + return P return P diff --git a/code/game/mecha/mech_fabricator.dm b/code/game/mecha/mech_fabricator.dm index 002753c5d4..bbbfa7f1bd 100644 --- a/code/game/mecha/mech_fabricator.dm +++ b/code/game/mecha/mech_fabricator.dm @@ -65,7 +65,7 @@ /obj/machinery/mecha_part_fabricator/Initialize(mapload) stored_research = new - rmat = AddComponent(/datum/component/remote_materials, "mechfab", mapload && link_on_init) + rmat = AddComponent(/datum/component/remote_materials, "mechfab", mapload && link_on_init, _after_insert=CALLBACK(src, .proc/AfterMaterialInsert)) RefreshParts() //Recalculating local material sizes if the fab isn't linked return ..() @@ -676,4 +676,3 @@ /obj/machinery/mecha_part_fabricator/offstation link_on_init = FALSE - circuit = /obj/item/circuitboard/machine/mechfab/offstation diff --git a/code/game/mecha/mecha.dm b/code/game/mecha/mecha.dm index 036aa6ed79..ce8171405f 100644 --- a/code/game/mecha/mecha.dm +++ b/code/game/mecha/mecha.dm @@ -132,8 +132,6 @@ events = new icon_state += "-open" add_radio() - add_cabin() - add_airtank() spark_system.set_up(2, 0, src) spark_system.attach(src) smoke_system.set_up(3, src) @@ -149,6 +147,12 @@ diag_hud_set_mechhealth() diag_hud_set_mechcell() diag_hud_set_mechstat() + return INITIALIZE_HINT_LATELOAD + +/obj/mecha/LateInitialize() + . = ..() + add_airtank() + add_cabin() /obj/mecha/get_cell() return cell @@ -251,9 +255,8 @@ cell = new /obj/item/stock_parts/cell/high/plus(src) /obj/mecha/proc/add_cabin() - cabin_air = new + cabin_air = new(200) cabin_air.set_temperature(T20C) - cabin_air.set_volume(200) cabin_air.set_moles(/datum/gas/oxygen,O2STANDARD*cabin_air.return_volume()/(R_IDEAL_GAS_EQUATION*cabin_air.return_temperature())) cabin_air.set_moles(/datum/gas/nitrogen,N2STANDARD*cabin_air.return_volume()/(R_IDEAL_GAS_EQUATION*cabin_air.return_temperature())) return cabin_air diff --git a/code/game/mecha/mecha_control_console.dm b/code/game/mecha/mecha_control_console.dm index 0e47872221..ab547f4ccd 100644 --- a/code/game/mecha/mecha_control_console.dm +++ b/code/game/mecha/mecha_control_console.dm @@ -29,7 +29,7 @@ integrity = round((M.obj_integrity / M.max_integrity) * 100), charge = M.cell ? round(M.cell.percent()) : null, airtank = M.internal_tank ? M.return_pressure() : null, - pilot = M.occupant, + pilot = list(M.occupant), location = get_area_name(M, TRUE), active_equipment = M.selected, emp_recharging = MT.recharging, @@ -38,7 +38,7 @@ if(istype(M, /obj/mecha/working/ripley)) var/obj/mecha/working/ripley/RM = M mech_data += list( - cargo_space = round((RM.cargo.len / RM.cargo_capacity) * 100) + cargo_space = round((LAZYLEN(RM.cargo) / RM.cargo_capacity) * 100) ) data["mechs"] += list(mech_data) @@ -46,7 +46,8 @@ return data /obj/machinery/computer/mecha/ui_act(action, params) - if(..()) + . = ..() + if(.) return switch(action) @@ -57,7 +58,7 @@ var/message = stripped_input(usr, "Input message", "Transmit message") var/obj/mecha/M = MT.chassis if(trim(message) && M) - M.occupant_message(message) + to_chat(M.occupant, message) to_chat(usr, "Message sent.") . = TRUE if("shock") @@ -67,8 +68,8 @@ var/obj/mecha/M = MT.chassis if(M) MT.shock() - log_game("[key_name(usr)] has activated remote EMP on exosuit [M], located at [loc_name(M)], which is currently [M.occupant? "being piloted by [key_name(M.occupant)]." : "without a pilot."] ") - message_admins("[key_name_admin(usr)][ADMIN_FLW(usr)] has activated remote EMP on exosuit [M][ADMIN_JMP(M)], which is currently [M.occupant ? "being piloted by [key_name_admin(M.occupant)][ADMIN_FLW(M.occupant)]." : "without a pilot."] ") + log_game("[key_name(usr)] has activated remote EMP on exosuit [M], located at [loc_name(M)], which [M.occupant ? "has the occupants [M.occupant]." : "without a pilot."] ") + message_admins("[key_name_admin(usr)][ADMIN_FLW(usr)] has activated remote EMP on exosuit [M][ADMIN_JMP(M)], which is currently [M.occupant ? "occupied by [M.occupant][ADMIN_FLW(M)]." : "without a pilot."] ") . = TRUE /obj/item/mecha_parts/mecha_tracking @@ -85,8 +86,8 @@ var/obj/mecha/chassis /** - * Returns a html formatted string describing attached mech status - */ + * Returns a html formatted string describing attached mech status + */ /obj/item/mecha_parts/mecha_tracking/proc/get_mecha_info() if(!chassis) return FALSE @@ -101,7 +102,7 @@ Active Equipment: [chassis.selected || "None"]"} if(istype(chassis, /obj/mecha/working/ripley)) var/obj/mecha/working/ripley/RM = chassis - answer += "
Used Cargo Space: [round((RM.cargo.len / RM.cargo_capacity * 100), 0.01)]%" + answer += "
Used Cargo Space: [round((LAZYLEN(RM.cargo) / RM.cargo_capacity * 100), 0.01)]%" return answer @@ -125,8 +126,8 @@ chassis = M /** - * Attempts to EMP mech that the tracker is attached to, if there is one and tracker is not on cooldown - */ + * Attempts to EMP mech that the tracker is attached to, if there is one and tracker is not on cooldown + */ /obj/item/mecha_parts/mecha_tracking/proc/shock() if(recharging) return @@ -136,8 +137,8 @@ recharging = TRUE /** - * Resets recharge variable, allowing tracker to be EMP pulsed again - */ + * Resets recharge variable, allowing tracker to be EMP pulsed again + */ /obj/item/mecha_parts/mecha_tracking/proc/recharge() recharging = FALSE diff --git a/code/game/objects/items/circuitboards/circuitboard.dm b/code/game/objects/items/circuitboards/circuitboard.dm index 6dc5db970d..3e7a68ef57 100644 --- a/code/game/objects/items/circuitboards/circuitboard.dm +++ b/code/game/objects/items/circuitboards/circuitboard.dm @@ -14,8 +14,41 @@ w_class = WEIGHT_CLASS_SMALL grind_results = list(/datum/reagent/silicon = 20) var/build_path = null + ///determines if the circuit board originated from a vendor off station or not. + var/onstation = TRUE /obj/item/circuitboard/proc/apply_default_parts(obj/machinery/M) + if(LAZYLEN(M.component_parts)) + // This really shouldn't happen. If it somehow does, print out a stack trace and gracefully handle it. + stack_trace("apply_defauly_parts called on machine that already had component_parts: [M]") + + // Move to nullspace so you don't trigger handle_atom_del logic and remove existing parts. + for(var/obj/item/part in M.component_parts) + part.moveToNullspace(loc) + qdel(part) + + // List of components always contains the circuit board used to build it. + M.component_parts = list(src) + forceMove(M) + + if(M.circuit != src) + // This really shouldn't happen. If it somehow does, print out a stack trace and gracefully handle it. + stack_trace("apply_default_parts called from a circuit board that does not belong to machine: [M]") + + // Move to nullspace so you don't trigger handle_atom_del logic, remove old circuit, add new circuit. + M.circuit.moveToNullspace() + qdel(M.circuit) + M.circuit = src + + return + +/** + * Used to allow the circuitboard to configure a machine in some way, shape or form. + * + * Arguments: + * * machine - The machine to attempt to configure. + */ +/obj/item/circuitboard/proc/configure_machine(obj/machinery/machine) return // Circuitboard/machine @@ -36,8 +69,8 @@ micro-manipulator, console screen, beaker, Microlaser, matter bin, power cells. if(!req_components) return - M.component_parts = list(src) // List of components always contains a board - moveToNullspace() + . = ..() + moveToNullspace() // thorw ourselves in nullspace for(var/comp_path in req_components) var/comp_amt = req_components[comp_path] diff --git a/code/game/objects/items/circuitboards/computer_circuitboards.dm b/code/game/objects/items/circuitboards/computer_circuitboards.dm index 8387c2704e..01bed631fa 100644 --- a/code/game/objects/items/circuitboards/computer_circuitboards.dm +++ b/code/game/objects/items/circuitboards/computer_circuitboards.dm @@ -1,58 +1,22 @@ -/obj/item/circuitboard/computer/turbine_computer - name = "Turbine Computer (Computer Board)" - build_path = /obj/machinery/computer/turbine_computer - -/obj/item/circuitboard/computer/launchpad_console - name = "Launchpad Control Console (Computer Board)" - build_path = /obj/machinery/computer/launchpad - -/obj/item/circuitboard/computer/message_monitor - name = "Message Monitor (Computer Board)" - build_path = /obj/machinery/computer/message_monitor - -/obj/item/circuitboard/computer/security - name = "Security Cameras (Computer Board)" - build_path = /obj/machinery/computer/security - -/obj/item/circuitboard/computer/security/shuttle - name = "Shuttlelinking Security Cameras (Computer Board)" - build_path = /obj/machinery/computer/security/shuttle - -/obj/item/circuitboard/computer/xenobiology - name = "circuit board (Xenobiology Console)" - build_path = /obj/machinery/computer/camera_advanced/xenobio - -/obj/item/circuitboard/computer/base_construction - name = "circuit board (Aux Mining Base Construction Console)" - build_path = /obj/machinery/computer/camera_advanced/base_construction +//Command /obj/item/circuitboard/computer/aiupload name = "AI Upload (Computer Board)" + icon_state = "command" build_path = /obj/machinery/computer/upload/ai /obj/item/circuitboard/computer/borgupload name = "Cyborg Upload (Computer Board)" + icon_state = "command" build_path = /obj/machinery/computer/upload/borg -/obj/item/circuitboard/computer/med_data - name = "Medical Records Console (Computer Board)" - build_path = /obj/machinery/computer/med_data - -/obj/item/circuitboard/computer/pandemic - name = "PanD.E.M.I.C. 2200 (Computer Board)" - build_path = /obj/machinery/computer/pandemic - -/obj/item/circuitboard/computer/scan_consolenew - name = "DNA Machine (Computer Board)" - build_path = /obj/machinery/computer/scan_consolenew - -/obj/item/circuitboard/computer/communications - name = "Communications (Computer Board)" - build_path = /obj/machinery/computer/communications - var/lastTimeUsed = 0 +/obj/item/circuitboard/computer/bsa_control + name = "Bluespace Artillery Controls (Computer Board)" + build_path = /obj/machinery/computer/bsa_control /obj/item/circuitboard/computer/card name = "ID Console (Computer Board)" + icon_state = "command" build_path = /obj/machinery/computer/card /obj/item/circuitboard/computer/card/centcom @@ -73,266 +37,238 @@ return ..() /obj/item/circuitboard/computer/card/minor/examine(user) - . = ..() - . += "Currently set to \"[dept_list[target_dept]]\"." + ..() + to_chat(user, "Currently set to \"[dept_list[target_dept]]\".") + //obj/item/circuitboard/computer/shield // name = "Shield Control (Computer Board)" +// icon_state = "command" // build_path = /obj/machinery/computer/stationshield -/obj/item/circuitboard/computer/teleporter - name = "Teleporter (Computer Board)" - build_path = /obj/machinery/computer/teleporter -/obj/item/circuitboard/computer/secure_data - name = "Security Records Console (Computer Board)" - build_path = /obj/machinery/computer/secure_data +//Engineering -/obj/item/circuitboard/computer/stationalert - name = "Station Alerts (Computer Board)" - build_path = /obj/machinery/computer/station_alert +/obj/item/circuitboard/computer/apc_control + name = "\improper Power Flow Control Console (Computer Board)" + icon_state = "engineering" + build_path = /obj/machinery/computer/apc_control + +/obj/item/circuitboard/computer/atmos_alert + name = "Atmospheric Alert (Computer Board)" + icon_state = "engineering" + build_path = /obj/machinery/computer/atmos_alert /obj/item/circuitboard/computer/atmos_control name = "Atmospheric Monitor (Computer Board)" + icon_state = "engineering" build_path = /obj/machinery/computer/atmos_control +/obj/item/circuitboard/computer/atmos_control/incinerator + name = "Incinerator Air Control (Computer Board)" + build_path = /obj/machinery/computer/atmos_control/incinerator + +/obj/item/circuitboard/computer/atmos_control/toxinsmix + name = "Toxins Mixing Air Control (Computer Board)" + build_path = /obj/machinery/computer/atmos_control/toxinsmix + /obj/item/circuitboard/computer/atmos_control/tank name = "Tank Control (Computer Board)" build_path = /obj/machinery/computer/atmos_control/tank -/obj/item/circuitboard/computer/atmos_alert - name = "Atmospheric Alert (Computer Board)" - build_path = /obj/machinery/computer/atmos_alert +/obj/item/circuitboard/computer/atmos_control/tank/oxygen_tank + name = "Oxygen Supply Control (Computer Board)" + build_path = /obj/machinery/computer/atmos_control/tank/oxygen_tank -/obj/item/circuitboard/computer/pod - name = "Massdriver control (Computer Board)" - build_path = /obj/machinery/computer/pod +/obj/item/circuitboard/computer/atmos_control/tank/toxin_tank + name = "Plasma Supply Control (Computer Board)" + build_path = /obj/machinery/computer/atmos_control/tank/toxin_tank -/obj/item/circuitboard/computer/robotics - name = "Robotics Control (Computer Board)" - build_path = /obj/machinery/computer/robotics +/obj/item/circuitboard/computer/atmos_control/tank/air_tank + name = "Mixed Air Supply Control (Computer Board)" + build_path = /obj/machinery/computer/atmos_control/tank/air_tank -/obj/item/circuitboard/computer/cloning - name = "Cloning (Computer Board)" - build_path = /obj/machinery/computer/cloning - var/list/records = list() +/obj/item/circuitboard/computer/atmos_control/tank/mix_tank + name = "Gas Mix Supply Control (Computer Board)" + build_path = /obj/machinery/computer/atmos_control/tank/mix_tank -/obj/item/circuitboard/computer/cloning/prototype - name = "Prototype Cloning (Computer Board)" - build_path = /obj/machinery/computer/cloning/prototype +/obj/item/circuitboard/computer/atmos_control/tank/nitrous_tank + name = "Nitrous Oxide Supply Control (Computer Board)" + build_path = /obj/machinery/computer/atmos_control/tank/nitrous_tank -/obj/item/circuitboard/computer/arcade/battle - name = "Arcade Battle (Computer Board)" - build_path = /obj/machinery/computer/arcade/battle +/obj/item/circuitboard/computer/atmos_control/tank/nitrogen_tank + name = "Nitrogen Supply Control (Computer Board)" + build_path = /obj/machinery/computer/atmos_control/tank/nitrogen_tank -/obj/item/circuitboard/computer/arcade/orion_trail - name = "Orion Trail (Computer Board)" - build_path = /obj/machinery/computer/arcade/orion_trail +/obj/item/circuitboard/computer/atmos_control/tank/carbon_tank + name = "Carbon Dioxide Supply Control (Computer Board)" + build_path = /obj/machinery/computer/atmos_control/tank/carbon_tank -/obj/item/circuitboard/computer/arcade/minesweeper - name = "Minesweeper (Computer Board)" - build_path = /obj/machinery/computer/arcade/minesweeper +// /obj/item/circuitboard/computer/atmos_control/tank/bz_tank +// name = "BZ Supply Control (Computer Board)" +// build_path = /obj/machinery/computer/atmos_control/tank/bz_tank -/obj/item/circuitboard/computer/arcade/amputation - name = "Mediborg's Amputation Adventure (Computer Board)" - build_path = /obj/machinery/computer/arcade/amputation +// /obj/item/circuitboard/computer/atmos_control/tank/freon_tank +// name = "Freon Supply Control (Computer Board)" +// build_path = /obj/machinery/computer/atmos_control/tank/freon_tank -/obj/item/circuitboard/computer/turbine_control - name = "Turbine control (Computer Board)" - build_path = /obj/machinery/computer/turbine_computer +// /obj/item/circuitboard/computer/atmos_control/tank/halon_tank +// name = "Halon Supply Control (Computer Board)" +// build_path = /obj/machinery/computer/atmos_control/tank/halon_tank -/obj/item/circuitboard/computer/solar_control - name = "Solar Control (Computer Board)" //name fixed 250810 - build_path = /obj/machinery/power/solar_control +// /obj/item/circuitboard/computer/atmos_control/tank/healium_tank +// name = "Healium Supply Control (Computer Board)" +// build_path = /obj/machinery/computer/atmos_control/tank/healium_tank -/obj/item/circuitboard/computer/powermonitor - name = "Power Monitor (Computer Board)" //name fixed 250810 - build_path = /obj/machinery/computer/monitor +// /obj/item/circuitboard/computer/atmos_control/tank/hydrogen_tank +// name = "Hydrogen Supply Control (Computer Board)" +// build_path = /obj/machinery/computer/atmos_control/tank/hydrogen_tank -/obj/item/circuitboard/computer/powermonitor/secret - name = "Outdated Power Monitor (Computer Board)" //Variant used on ruins to prevent them from showing up on PDA's. - build_path = /obj/machinery/computer/monitor/secret +// /obj/item/circuitboard/computer/atmos_control/tank/hypernoblium_tank +// name = "Hypernoblium Supply Control (Computer Board)" +// build_path = /obj/machinery/computer/atmos_control/tank/hypernoblium_tank -/obj/item/circuitboard/computer/olddoor - name = "DoorMex (Computer Board)" - build_path = /obj/machinery/computer/pod/old +// /obj/item/circuitboard/computer/atmos_control/tank/miasma_tank +// name = "Miasma Supply Control (Computer Board)" +// build_path = /obj/machinery/computer/atmos_control/tank/miasma_tank -/obj/item/circuitboard/computer/syndicatedoor - name = "ProComp Executive (Computer Board)" - build_path = /obj/machinery/computer/pod/old/syndicate +// /obj/item/circuitboard/computer/atmos_control/tank/nitryl_tank +// name = "Nitryl Supply Control (Computer Board)" +// build_path = /obj/machinery/computer/atmos_control/tank/nitryl_tank -/obj/item/circuitboard/computer/swfdoor - name = "Magix (Computer Board)" - build_path = /obj/machinery/computer/pod/old/swf +// /obj/item/circuitboard/computer/atmos_control/tank/pluoxium_tank +// name = "Pluoxium Supply Control (Computer Board)" +// build_path = /obj/machinery/computer/atmos_control/tank/pluoxium_tank -/obj/item/circuitboard/computer/prisoner - name = "Prisoner Management Console (Computer Board)" - build_path = /obj/machinery/computer/prisoner/management +// /obj/item/circuitboard/computer/atmos_control/tank/proto_nitrate_tank +// name = "Proto-Nitrate Supply Control (Computer Board)" +// build_path = /obj/machinery/computer/atmos_control/tank/proto_nitrate_tank -/obj/item/circuitboard/computer/gulag_teleporter_console - name = "Labor Camp teleporter console (Computer Board)" - build_path = /obj/machinery/computer/prisoner/gulag_teleporter_computer +// /obj/item/circuitboard/computer/atmos_control/tank/stimulum_tank +// name = "Stimulum Supply Control (Computer Board)" +// build_path = /obj/machinery/computer/atmos_control/tank/stimulum_tank -/obj/item/circuitboard/computer/rdconsole/production - name = "R&D Console Production Only (Computer Board)" - build_path = /obj/machinery/computer/rdconsole/production +// /obj/item/circuitboard/computer/atmos_control/tank/tritium_tank +// name = "Tritium Supply Control (Computer Board)" +// build_path = /obj/machinery/computer/atmos_control/tank/tritium_tank -/obj/item/circuitboard/computer/rdconsole - name = "R&D Console (Computer Board)" - build_path = /obj/machinery/computer/rdconsole/core +// /obj/item/circuitboard/computer/atmos_control/tank/water_vapor +// name = "Water Vapor Supply Control (Computer Board)" +// build_path = /obj/machinery/computer/atmos_control/tank/water_vapor -/obj/item/circuitboard/computer/rdconsole/attackby(obj/item/I, mob/user, params) - if(I.tool_behaviour == TOOL_SCREWDRIVER) - if(build_path == /obj/machinery/computer/rdconsole/core) - name = "R&D Console - Robotics (Computer Board)" - build_path = /obj/machinery/computer/rdconsole/robotics - to_chat(user, "Access protocols successfully updated.") - else - name = "R&D Console (Computer Board)" - build_path = /obj/machinery/computer/rdconsole/core - to_chat(user, "Defaulting access protocols.") - else - return ..() +// /obj/item/circuitboard/computer/atmos_control/tank/zauker_tank +// name = "Zauker Supply Control (Computer Board)" +// build_path = /obj/machinery/computer/atmos_control/tank/zauker_tank -/obj/item/circuitboard/computer/mecha_control - name = "Exosuit Control Console (Computer Board)" - build_path = /obj/machinery/computer/mecha +// /obj/item/circuitboard/computer/atmos_control/tank/helium_tank +// name = "Helium Supply Control (Computer Board)" +// build_path = /obj/machinery/computer/atmos_control/tank/helium_tank -/obj/item/circuitboard/computer/rdservercontrol - name = "R&D Server Control (Computer Board)" - build_path = /obj/machinery/computer/rdservercontrol +// /obj/item/circuitboard/computer/atmos_control/tank/antinoblium_tank +// name = "Antinoblium Supply Control (Computer Board)" +// build_path = /obj/machinery/computer/atmos_control/tank/antinoblium_tank -/obj/item/circuitboard/computer/crew - name = "Crew Monitoring Console (Computer Board)" - build_path = /obj/machinery/computer/crew +/obj/item/circuitboard/computer/auxiliary_base + name = "Auxiliary Base Management Console (Computer Board)" + icon_state = "engineering" + build_path = /obj/machinery/computer/auxillary_base -/obj/item/circuitboard/computer/mech_bay_power_console - name = "Mech Bay Power Control Console (Computer Board)" - build_path = /obj/machinery/computer/mech_bay_power_console +/obj/item/circuitboard/computer/base_construction + name = "circuit board (Generic Base Construction Console)" + icon_state = "engineering" + build_path = /obj/machinery/computer/camera_advanced/base_construction -/obj/item/circuitboard/computer/cargo - name = "Supply Console (Computer Board)" - build_path = /obj/machinery/computer/cargo - var/contraband = FALSE +// /obj/item/circuitboard/computer/base_construction/aux +// name = "circuit board (Aux Mining Base Construction Console)" +// icon_state = "engineering" +// build_path = /obj/machinery/computer/camera_advanced/base_construction/aux -/obj/item/circuitboard/computer/cargo/multitool_act(mob/living/user) - if(!(obj_flags & EMAGGED)) - contraband = !contraband - to_chat(user, "Receiver spectrum set to [contraband ? "Broad" : "Standard"].") - else - to_chat(user, "The spectrum chip is unresponsive.") - -/obj/item/circuitboard/computer/cargo/emag_act(mob/living/user) - . = ..() - if(obj_flags & EMAGGED) - return - contraband = TRUE - obj_flags |= EMAGGED - to_chat(user, "You adjust [src]'s routing and receiver spectrum, unlocking special supplies and contraband.") - return TRUE - -/obj/item/circuitboard/computer/cargo/express - name = "Express Supply Console (Computer Board)" - build_path = /obj/machinery/computer/cargo/express - -/obj/item/circuitboard/computer/cargo/express/multitool_act(mob/living/user) - if (!(obj_flags & EMAGGED)) - to_chat(user, "Routing protocols are already set to: \"factory defaults\".") - else - to_chat(user, "You reset the routing protocols to: \"factory defaults\".") - obj_flags &= ~EMAGGED - -/obj/item/circuitboard/computer/cargo/express/emag_act(mob/living/user) - . = SEND_SIGNAL(src, COMSIG_ATOM_EMAG_ACT) - if(obj_flags & EMAGGED) - return - to_chat(user, "You change the routing protocols, allowing the Drop Pod to land anywhere on the station.") - obj_flags |= EMAGGED - return TRUE - -/obj/item/circuitboard/computer/cargo/request - name = "Supply Request Console (Computer Board)" - build_path = /obj/machinery/computer/cargo/request - -/obj/item/circuitboard/computer/bounty - name = "Nanotrasen Bounty Console (Computer Board)" - build_path = /obj/machinery/computer/bounty - -/obj/item/circuitboard/computer/operating - name = "Operating Computer (Computer Board)" - build_path = /obj/machinery/computer/operating - -/obj/item/circuitboard/computer/mining - name = "Outpost Status Display (Computer Board)" - build_path = /obj/machinery/computer/security/mining - -/obj/item/circuitboard/computer/research - name = "Research Monitor (Computer Board)" - build_path = /obj/machinery/computer/security/research +// /obj/item/circuitboard/computer/base_construction/centcom +// name = "circuit board (Centcom Base Construction Console)" +// icon_state = "engineering" +// build_path = /obj/machinery/computer/camera_advanced/base_construction/centcom /obj/item/circuitboard/computer/comm_monitor name = "Telecommunications Monitor (Computer Board)" + icon_state = "engineering" build_path = /obj/machinery/computer/telecomms/monitor /obj/item/circuitboard/computer/comm_server name = "Telecommunications Server Monitor (Computer Board)" + icon_state = "engineering" build_path = /obj/machinery/computer/telecomms/server -/obj/item/circuitboard/computer/labor_shuttle - name = "Labor Shuttle (Computer Board)" - build_path = /obj/machinery/computer/shuttle/labor +/obj/item/circuitboard/computer/communications + name = "Communications (Computer Board)" + icon_state = "engineering" + build_path = /obj/machinery/computer/communications -/obj/item/circuitboard/computer/labor_shuttle/one_way - name = "Prisoner Shuttle Console (Computer Board)" - build_path = /obj/machinery/computer/shuttle/labor/one_way +/obj/item/circuitboard/computer/message_monitor + name = "Message Monitor (Computer Board)" + icon_state = "engineering" + build_path = /obj/machinery/computer/message_monitor -/obj/item/circuitboard/computer/ferry - name = "Transport Ferry (Computer Board)" - build_path = /obj/machinery/computer/shuttle/ferry +/obj/item/circuitboard/computer/powermonitor + name = "Power Monitor (Computer Board)" //name fixed 250810 + icon_state = "engineering" + build_path = /obj/machinery/computer/monitor -/obj/item/circuitboard/computer/ferry/request - name = "Transport Ferry Console (Computer Board)" - build_path = /obj/machinery/computer/shuttle/ferry/request +/obj/item/circuitboard/computer/powermonitor/secret + name = "Outdated Power Monitor (Computer Board)" //Variant used on ruins to prevent them from showing up on PDA's. + icon_state = "engineering" + build_path = /obj/machinery/computer/monitor/secret -/obj/item/circuitboard/computer/mining_shuttle - name = "Mining Shuttle (Computer Board)" - build_path = /obj/machinery/computer/shuttle/mining +/obj/item/circuitboard/computer/sat_control + name = "Satellite Network Control (Computer Board)" + icon_state = "engineering" + build_path = /obj/machinery/computer/sat_control -/obj/item/circuitboard/computer/mining_shuttle/common - name = "Lavaland Shuttle (Computer Board)" - build_path = /obj/machinery/computer/shuttle/mining/common +/obj/item/circuitboard/computer/solar_control + name = "Solar Control (Computer Board)" //name fixed 250810 + icon_state = "engineering" + build_path = /obj/machinery/power/solar_control -/obj/item/circuitboard/computer/snow_taxi - name = "Snow Taxi (Computer Board)" - build_path = /obj/machinery/computer/shuttle/snow_taxi +/obj/item/circuitboard/computer/stationalert + name = "Station Alerts (Computer Board)" + icon_state = "engineering" + build_path = /obj/machinery/computer/station_alert -/obj/item/circuitboard/computer/white_ship - name = "White Ship (Computer Board)" - build_path = /obj/machinery/computer/shuttle/white_ship +/obj/item/circuitboard/computer/turbine_computer + name = "Turbine Computer (Computer Board)" + icon_state = "engineering" + build_path = /obj/machinery/computer/turbine_computer -/obj/item/circuitboard/computer/white_ship/pod - name = "Salvage Pod (Computer Board)" - build_path = /obj/machinery/computer/shuttle/white_ship/pod +/obj/item/circuitboard/computer/turbine_control + name = "Turbine control (Computer Board)" + icon_state = "engineering" + build_path = /obj/machinery/computer/turbine_computer -/obj/item/circuitboard/computer/white_ship/pod/recall - name = "Salvage Pod Recall (Computer Board)" - build_path = /obj/machinery/computer/shuttle/white_ship/pod/recall +//Generic -/obj/item/circuitboard/computer/auxillary_base - name = "Auxillary Base Management Console (Computer Board)" - build_path = /obj/machinery/computer/auxillary_base +/obj/item/circuitboard/computer/arcade/amputation + name = "Mediborg's Amputation Adventure (Computer Board)" + icon_state = "generic" + build_path = /obj/machinery/computer/arcade/amputation + +/obj/item/circuitboard/computer/arcade/battle + name = "Arcade Battle (Computer Board)" + icon_state = "generic" + build_path = /obj/machinery/computer/arcade/battle + +/obj/item/circuitboard/computer/arcade/orion_trail + name = "Orion Trail (Computer Board)" + + build_path = /obj/machinery/computer/arcade/orion_trail + +/obj/item/circuitboard/computer/arcade/minesweeper + name = "Minesweeper (Computer Board)" + icon_state = "generic" + build_path = /obj/machinery/computer/arcade/minesweeper /obj/item/circuitboard/computer/holodeck// Not going to let people get this, but it's just here for future name = "Holodeck Control (Computer Board)" + icon_state = "generic" build_path = /obj/machinery/computer/holodeck -/obj/item/circuitboard/computer/aifixer - name = "AI Integrity Restorer (Computer Board)" - build_path = /obj/machinery/computer/aifixer - -/obj/item/circuitboard/computer/slot_machine - name = "Slot Machine (Computer Board)" - build_path = /obj/machinery/computer/slot_machine - /obj/item/circuitboard/computer/libraryconsole name = "Library Visitor Console (Computer Board)" build_path = /obj/machinery/computer/libraryconsole @@ -350,16 +286,34 @@ else return ..() -/obj/item/circuitboard/computer/apc_control - name = "\improper Power Flow Control Console (Computer Board)" - build_path = /obj/machinery/computer/apc_control - /obj/item/circuitboard/computer/monastery_shuttle name = "Monastery Shuttle (Computer Board)" + icon_state = "generic" build_path = /obj/machinery/computer/shuttle/monastery_shuttle +/obj/item/circuitboard/computer/olddoor + name = "DoorMex (Computer Board)" + icon_state = "generic" + build_path = /obj/machinery/computer/pod/old + +/obj/item/circuitboard/computer/pod + name = "Massdriver control (Computer Board)" + icon_state = "generic" + build_path = /obj/machinery/computer/pod + +/obj/item/circuitboard/computer/slot_machine + name = "Slot Machine (Computer Board)" + icon_state = "generic" + build_path = /obj/machinery/computer/slot_machine + +/obj/item/circuitboard/computer/swfdoor + name = "Magix (Computer Board)" + icon_state = "generic" + build_path = /obj/machinery/computer/pod/old/swf + /obj/item/circuitboard/computer/syndicate_shuttle name = "Syndicate Shuttle (Computer Board)" + icon_state = "generic" build_path = /obj/machinery/computer/shuttle/syndicate var/challenge = FALSE var/moved = FALSE @@ -372,26 +326,296 @@ GLOB.syndicate_shuttle_boards -= src return ..() -/obj/item/circuitboard/computer/bsa_control - name = "Bluespace Artillery Controls (Computer Board)" - build_path = /obj/machinery/computer/bsa_control +/obj/item/circuitboard/computer/syndicatedoor + name = "ProComp Executive (Computer Board)" + icon_state = "generic" + build_path = /obj/machinery/computer/pod/old/syndicate -/obj/item/circuitboard/computer/sat_control - name = "Satellite Network Control (Computer Board)" - build_path = /obj/machinery/computer/sat_control +/obj/item/circuitboard/computer/white_ship + name = "White Ship (Computer Board)" + icon_state = "generic" + build_path = /obj/machinery/computer/shuttle/white_ship + +// /obj/item/circuitboard/computer/white_ship/bridge +// name = "White Ship Bridge (Computer Board)" +// icon_state = "generic" +// build_path = /obj/machinery/computer/shuttle/white_ship/bridge + +/obj/item/circuitboard/computer/white_ship/pod + name = "Salvage Pod (Computer Board)" + build_path = /obj/machinery/computer/shuttle/white_ship/pod + +/obj/item/circuitboard/computer/white_ship/pod/recall + name = "Salvage Pod Recall (Computer Board)" + build_path = /obj/machinery/computer/shuttle/white_ship/pod/recall + +/obj/item/circuitboard/computer/snow_taxi + name = "Snow Taxi (Computer Board)" + build_path = /obj/machinery/computer/shuttle/snow_taxi + +// /obj/item/circuitboard/computer/bountypad +// name = "Bounty Pad (Computer Board)" +// build_path = /obj/machinery/computer/piratepad_control/civilian + +/obj/item/circuitboard/computer/security/shuttle + name = "Shuttlelinking Security Cameras (Computer Board)" + icon_state = "generic" + build_path = /obj/machinery/computer/security/shuttle + +//Medical + +/obj/item/circuitboard/computer/crew + name = "Crew Monitoring Console (Computer Board)" + icon_state = "medical" + build_path = /obj/machinery/computer/crew + +/obj/item/circuitboard/computer/med_data + name = "Medical Records Console (Computer Board)" + icon_state = "medical" + build_path = /obj/machinery/computer/med_data + +/obj/item/circuitboard/computer/operating + name = "Operating Computer (Computer Board)" + icon_state = "medical" + build_path = /obj/machinery/computer/operating + +/obj/item/circuitboard/computer/pandemic + name = "PanD.E.M.I.C. 2200 (Computer Board)" + icon_state = "medical" + build_path = /obj/machinery/computer/pandemic + +/obj/item/circuitboard/computer/cloning + name = "Cloning (Computer Board)" + icon_state = "medical" + build_path = /obj/machinery/computer/cloning + var/list/records = list() + +/obj/item/circuitboard/computer/cloning/prototype + name = "Prototype Cloning (Computer Board)" + build_path = /obj/machinery/computer/cloning/prototype + +//Science + +/obj/item/circuitboard/computer/aifixer + name = "AI Integrity Restorer (Computer Board)" + icon_state = "science" + build_path = /obj/machinery/computer/aifixer + +/obj/item/circuitboard/computer/launchpad_console + name = "Launchpad Control Console (Computer Board)" + icon_state = "science" + build_path = /obj/machinery/computer/launchpad + +/obj/item/circuitboard/computer/mech_bay_power_console + name = "Mech Bay Power Control Console (Computer Board)" + icon_state = "science" + build_path = /obj/machinery/computer/mech_bay_power_console + +/obj/item/circuitboard/computer/mecha_control + name = "Exosuit Control Console (Computer Board)" + icon_state = "science" + build_path = /obj/machinery/computer/mecha /obj/item/circuitboard/computer/nanite_chamber_control name = "Nanite Chamber Control (Computer Board)" + icon_state = "science" build_path = /obj/machinery/computer/nanite_chamber_control /obj/item/circuitboard/computer/nanite_cloud_controller name = "Nanite Cloud Control (Computer Board)" + icon_state = "science" build_path = /obj/machinery/computer/nanite_cloud_controller +/obj/item/circuitboard/computer/rdconsole/production + name = "R&D Console Production Only (Computer Board)" + icon_state = "science" + build_path = /obj/machinery/computer/rdconsole/production -/obj/item/circuitboard/computer/shuttle/flight_control - name = "Shuttle Flight Control (Computer Board)" - build_path = /obj/machinery/computer/custom_shuttle +/obj/item/circuitboard/computer/rdconsole + name = "R&D Console (Computer Board)" + icon_state = "science" + build_path = /obj/machinery/computer/rdconsole/core + +/obj/item/circuitboard/computer/rdconsole/attackby(obj/item/I, mob/user, params) + if(I.tool_behaviour == TOOL_SCREWDRIVER) + if(build_path == /obj/machinery/computer/rdconsole/core) + name = "R&D Console - Robotics (Computer Board)" + build_path = /obj/machinery/computer/rdconsole/robotics + to_chat(user, "Access protocols successfully updated.") + else + name = "R&D Console (Computer Board)" + build_path = /obj/machinery/computer/rdconsole/core + to_chat(user, "Defaulting access protocols.") + else + return ..() + +/obj/item/circuitboard/computer/rdservercontrol + name = "R&D Server Control (Computer Board)" + icon_state = "science" + build_path = /obj/machinery/computer/rdservercontrol + +/obj/item/circuitboard/computer/research + name = "Research Monitor (Computer Board)" + icon_state = "science" + build_path = /obj/machinery/computer/security/research + +/obj/item/circuitboard/computer/robotics + name = "Robotics Control (Computer Board)" + icon_state = "science" + build_path = /obj/machinery/computer/robotics + +/obj/item/circuitboard/computer/teleporter + name = "Teleporter (Computer Board)" + icon_state = "science" + build_path = /obj/machinery/computer/teleporter + +/obj/item/circuitboard/computer/xenobiology + name = "Xenobiology Console (Computer Board)" + icon_state = "science" + build_path = /obj/machinery/computer/camera_advanced/xenobio + +/obj/item/circuitboard/computer/scan_consolenew + name = "DNA Console (Computer Board)" + icon_state = "science" + build_path = /obj/machinery/computer/scan_consolenew + +// /obj/item/circuitboard/computer/mechpad +// name = "Mecha Orbital Pad Console (Computer Board)" +// icon_state = "science" +// build_path = /obj/machinery/computer/mechpad + +//Security + +/obj/item/circuitboard/computer/labor_shuttle + name = "Labor Shuttle (Computer Board)" + icon_state = "security" + build_path = /obj/machinery/computer/shuttle/labor + +/obj/item/circuitboard/computer/labor_shuttle/one_way + name = "Prisoner Shuttle Console (Computer Board)" + icon_state = "security" + build_path = /obj/machinery/computer/shuttle/labor/one_way + +/obj/item/circuitboard/computer/gulag_teleporter_console + name = "Labor Camp teleporter console (Computer Board)" + icon_state = "security" + build_path = /obj/machinery/computer/prisoner/gulag_teleporter_computer + +/obj/item/circuitboard/computer/prisoner + name = "Prisoner Management Console (Computer Board)" + icon_state = "security" + build_path = /obj/machinery/computer/prisoner/management + +/obj/item/circuitboard/computer/secure_data + name = "Security Records Console (Computer Board)" + icon_state = "security" + build_path = /obj/machinery/computer/secure_data + +// /obj/item/circuitboard/computer/warrant +// name = "Security Warrant Viewer (Computer Board)" +// icon_state = "security" +// build_path = /obj/machinery/computer/warrant + +/obj/item/circuitboard/computer/security + name = "Security Cameras (Computer Board)" + icon_state = "security" + build_path = /obj/machinery/computer/security + +/obj/item/circuitboard/computer/advanced_camera + name = "Advanced Camera Console (Computer Board)" + icon_state = "security" + build_path = /obj/machinery/computer/camera_advanced/syndie + +//Service + +//Supply + +/obj/item/circuitboard/computer/cargo + name = "Supply Console (Computer Board)" + icon_state = "supply" + build_path = /obj/machinery/computer/cargo + var/contraband = FALSE + +/obj/item/circuitboard/computer/cargo/multitool_act(mob/living/user) + . = ..() + if(!(obj_flags & EMAGGED)) + contraband = !contraband + to_chat(user, "Receiver spectrum set to [contraband ? "Broad" : "Standard"].") + else + to_chat(user, "The spectrum chip is unresponsive.") + +/obj/item/circuitboard/computer/cargo/emag_act(mob/living/user) + . = ..() + if(!(obj_flags & EMAGGED)) + contraband = TRUE + obj_flags |= EMAGGED + to_chat(user, "You adjust [src]'s routing and receiver spectrum, unlocking special supplies and contraband.") + +/obj/item/circuitboard/computer/cargo/configure_machine(obj/machinery/computer/cargo/machine) + if(!istype(machine)) + CRASH("Cargo board attempted to configure incorrect machine type: [machine] ([machine?.type])") + + machine.contraband = contraband + if (obj_flags & EMAGGED) + machine.obj_flags |= EMAGGED + else + machine.obj_flags &= ~EMAGGED + +/obj/item/circuitboard/computer/cargo/express + name = "Express Supply Console (Computer Board)" + build_path = /obj/machinery/computer/cargo/express + +/obj/item/circuitboard/computer/cargo/express/emag_act(mob/living/user) + if(!(obj_flags & EMAGGED)) + contraband = TRUE + obj_flags |= EMAGGED + to_chat(user, "You change the routing protocols, allowing the Drop Pod to land anywhere on the station.") + +/obj/item/circuitboard/computer/cargo/express/multitool_act(mob/living/user) + if (!(obj_flags & EMAGGED)) + contraband = !contraband + to_chat(user, "Receiver spectrum set to [contraband ? "Broad" : "Standard"].") + else + to_chat(user, "You reset the destination-routing protocols and receiver spectrum to factory defaults.") + contraband = FALSE + obj_flags &= ~EMAGGED + +/obj/item/circuitboard/computer/cargo/request + name = "Supply Request Console (Computer Board)" + build_path = /obj/machinery/computer/cargo/request + +/obj/item/circuitboard/computer/bounty + name = "Nanotrasen Bounty Console (Computer Board)" + build_path = /obj/machinery/computer/bounty + +/obj/item/circuitboard/computer/ferry + name = "Transport Ferry (Computer Board)" + icon_state = "supply" + build_path = /obj/machinery/computer/shuttle/ferry + +/obj/item/circuitboard/computer/ferry/request + name = "Transport Ferry Console (Computer Board)" + icon_state = "supply" + build_path = /obj/machinery/computer/shuttle/ferry/request + +/obj/item/circuitboard/computer/mining + name = "Outpost Status Display (Computer Board)" + icon_state = "supply" + build_path = /obj/machinery/computer/security/mining + +/obj/item/circuitboard/computer/mining_shuttle + name = "Mining Shuttle (Computer Board)" + icon_state = "supply" + build_path = /obj/machinery/computer/shuttle/mining + +/obj/item/circuitboard/computer/mining_shuttle/common + name = "Lavaland Shuttle (Computer Board)" + build_path = /obj/machinery/computer/shuttle/mining/common /obj/item/circuitboard/computer/shuttle/docker name = "Shuttle Navigation Computer (Computer Board)" build_path = /obj/machinery/computer/camera_advanced/shuttle_docker/custom + +// DIY shuttle +/obj/item/circuitboard/computer/shuttle/flight_control + name = "Shuttle Flight Control (Computer Board)" + build_path = /obj/machinery/computer/custom_shuttle diff --git a/code/game/objects/items/circuitboards/machine_circuitboards.dm b/code/game/objects/items/circuitboards/machine_circuitboards.dm index e0ea5f3bac..dcd0cda233 100644 --- a/code/game/objects/items/circuitboards/machine_circuitboards.dm +++ b/code/game/objects/items/circuitboards/machine_circuitboards.dm @@ -1,27 +1,43 @@ -/obj/item/circuitboard/machine/sleeper - name = "Sleeper (Machine Board)" - build_path = /obj/machinery/sleeper - req_components = list( - /obj/item/stock_parts/matter_bin = 1, - /obj/item/stock_parts/manipulator = 1, - /obj/item/stack/cable_coil = 1, - /obj/item/stack/sheet/glass = 2) +//Command -/obj/item/circuitboard/machine/sleeper/syndie - name = "Syndicate Sleeper (Machine Board)" - build_path = /obj/machinery/sleeper/syndie - -/obj/item/circuitboard/machine/vr_sleeper - name = "VR Sleeper (Machine Board)" - build_path = /obj/machinery/vr_sleeper +/obj/item/circuitboard/machine/bsa/back + name = "Bluespace Artillery Generator (Machine Board)" + icon_state = "command" + build_path = /obj/machinery/bsa/back //No freebies! req_components = list( - /obj/item/stock_parts/manipulator = 1, - /obj/item/stack/cable_coil = 1, - /obj/item/stock_parts/scanning_module = 2, - /obj/item/stack/sheet/glass = 2) + /obj/item/stock_parts/capacitor/quadratic = 5, + /obj/item/stack/cable_coil = 2) + +/obj/item/circuitboard/machine/bsa/front + name = "Bluespace Artillery Bore (Machine Board)" + icon_state = "command" + build_path = /obj/machinery/bsa/front + req_components = list( + /obj/item/stock_parts/manipulator/femto = 5, + /obj/item/stack/cable_coil = 2) + +/obj/item/circuitboard/machine/bsa/middle + name = "Bluespace Artillery Fusor (Machine Board)" + icon_state = "command" + build_path = /obj/machinery/bsa/middle + req_components = list( + /obj/item/stack/ore/bluespace_crystal = 20, + /obj/item/stack/cable_coil = 2) + +/obj/item/circuitboard/machine/dna_vault + name = "DNA Vault (Machine Board)" + icon_state = "command" + build_path = /obj/machinery/dna_vault //No freebies! + req_components = list( + /obj/item/stock_parts/capacitor/super = 5, + /obj/item/stock_parts/manipulator/pico = 5, + /obj/item/stack/cable_coil = 2) + +//Engineering /obj/item/circuitboard/machine/announcement_system name = "Announcement System (Machine Board)" + icon_state = "engineering" build_path = /obj/machinery/announcement_system req_components = list( /obj/item/stack/cable_coil = 2, @@ -29,6 +45,7 @@ /obj/item/circuitboard/machine/autolathe name = "Autolathe (Machine Board)" + icon_state = "engineering" build_path = /obj/machinery/autolathe req_components = list( /obj/item/stock_parts/matter_bin = 3, @@ -39,133 +56,72 @@ name = "Secure Autolathe (Machine Board)" build_path = /obj/machinery/autolathe/secure -/obj/item/circuitboard/machine/bloodbankgen - name = "Blood Bank Generator (Machine Board)" - build_path = /obj/machinery/bloodbankgen +// why is this not a subtype of autolathe? +/obj/item/circuitboard/machine/autolathe/toy + name = "Autoylathe (Machine Board)" + icon_state = "engineering" + build_path = /obj/machinery/autolathe/toy req_components = list( - /obj/item/stock_parts/matter_bin = 1, + /obj/item/stock_parts/matter_bin = 3, /obj/item/stock_parts/manipulator = 1, - /obj/item/stack/cable_coil = 5, /obj/item/stack/sheet/glass = 1) -/obj/item/circuitboard/machine/medipen_refiller - name = "Medipen Refiller (Machine Board)" - icon_state = "medical" - build_path = /obj/machinery/medipen_refiller - req_components = list( - /obj/item/stock_parts/matter_bin = 1) - -/obj/item/circuitboard/machine/clonepod - name = "Clone Pod (Machine Board)" - build_path = /obj/machinery/clonepod - req_components = list( - /obj/item/stack/cable_coil = 2, - /obj/item/stock_parts/scanning_module = 2, - /obj/item/stock_parts/manipulator = 2, - /obj/item/stack/sheet/glass = 1) - -/obj/item/circuitboard/machine/clonepod/experimental - name = "Experimental Clone Pod (Machine Board)" - build_path = /obj/machinery/clonepod/experimental - -/obj/item/circuitboard/machine/sheetifier - name = "Sheet-meister 2000 (Machine Board)" - icon_state = "supply" - build_path = /obj/machinery/sheetifier - req_components = list( - /obj/item/stock_parts/manipulator = 2, - /obj/item/stock_parts/matter_bin = 2) - -/obj/item/circuitboard/machine/abductor - name = "alien board (Report This)" - icon_state = "abductor_mod" - -/obj/item/circuitboard/machine/clockwork - name = "clockwork board (Report This)" - icon_state = "clock_mod" - -/obj/item/circuitboard/machine/clonescanner - name = "Cloning Scanner (Machine Board)" - build_path = /obj/machinery/dna_scannernew - req_components = list( - /obj/item/stock_parts/scanning_module = 1, - /obj/item/stock_parts/matter_bin = 1, - /obj/item/stock_parts/micro_laser = 1, - /obj/item/stack/sheet/glass = 1, - /obj/item/stack/cable_coil = 2) - -/obj/item/circuitboard/machine/holopad - name = "AI Holopad (Machine Board)" - build_path = /obj/machinery/holopad - req_components = list(/obj/item/stock_parts/capacitor = 1) - needs_anchored = FALSE //wew lad - -/obj/item/circuitboard/machine/launchpad - name = "Bluespace Launchpad (Machine Board)" - build_path = /obj/machinery/launchpad - req_components = list( - /obj/item/stack/ore/bluespace_crystal = 1, - /obj/item/stock_parts/manipulator = 1) - def_components = list(/obj/item/stack/ore/bluespace_crystal = /obj/item/stack/ore/bluespace_crystal/artificial) - -/obj/item/circuitboard/machine/limbgrower - name = "Limb Grower (Machine Board)" - build_path = /obj/machinery/limbgrower - req_components = list( - /obj/item/stock_parts/manipulator = 1, - /obj/item/reagent_containers/glass/beaker = 2, - /obj/item/stack/sheet/glass = 1) - -/obj/item/circuitboard/machine/quantumpad - name = "Quantum Pad (Machine Board)" - build_path = /obj/machinery/quantumpad - req_components = list( - /obj/item/stack/ore/bluespace_crystal = 1, - /obj/item/stock_parts/capacitor = 1, - /obj/item/stock_parts/manipulator = 1, - /obj/item/stack/cable_coil = 1) - def_components = list(/obj/item/stack/ore/bluespace_crystal = /obj/item/stack/ore/bluespace_crystal/artificial) - -/obj/item/circuitboard/machine/recharger - name = "Weapon Recharger (Machine Board)" - build_path = /obj/machinery/recharger +/obj/item/circuitboard/machine/tesla_coil + name = "Tesla Controller (Machine Board)" + desc = "You can use a screwdriver to switch between Research and Power Generation." + icon_state = "engineering" + build_path = /obj/machinery/power/tesla_coil req_components = list(/obj/item/stock_parts/capacitor = 1) needs_anchored = FALSE -/obj/item/circuitboard/machine/cell_charger - name = "Cell Charger (Machine Board)" - build_path = /obj/machinery/cell_charger +#define PATH_POWERCOIL /obj/machinery/power/tesla_coil/power +#define PATH_RPCOIL /obj/machinery/power/tesla_coil/research + +/obj/item/circuitboard/machine/tesla_coil/Initialize() + . = ..() + if(build_path) + build_path = PATH_POWERCOIL + +/obj/item/circuitboard/machine/tesla_coil/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/screwdriver)) + var/obj/item/circuitboard/new_type + var/new_setting + switch(build_path) + if(PATH_POWERCOIL) + new_type = /obj/item/circuitboard/machine/tesla_coil/research + new_setting = "Research" + if(PATH_RPCOIL) + new_type = /obj/item/circuitboard/machine/tesla_coil/power + new_setting = "Power" + name = initial(new_type.name) + build_path = initial(new_type.build_path) + I.play_tool_sound(src) + to_chat(user, "You change the circuitboard setting to \"[new_setting]\".") + else + return ..() + +/obj/item/circuitboard/machine/tesla_coil/power + name = "Tesla Coil (Machine Board)" + build_path = PATH_POWERCOIL + +/obj/item/circuitboard/machine/tesla_coil/research + name = "Tesla Corona Researcher (Machine Board)" + build_path = PATH_RPCOIL + +#undef PATH_POWERCOIL +#undef PATH_RPCOIL + +/obj/item/circuitboard/machine/grounding_rod + name = "Grounding Rod (Machine Board)" + icon_state = "engineering" + build_path = /obj/machinery/power/grounding_rod req_components = list(/obj/item/stock_parts/capacitor = 1) needs_anchored = FALSE -/obj/item/circuitboard/machine/cyborgrecharger - name = "Cyborg Recharger (Machine Board)" - build_path = /obj/machinery/recharge_station - req_components = list( - /obj/item/stock_parts/capacitor = 2, - /obj/item/stock_parts/cell = 1, - /obj/item/stock_parts/manipulator = 1) - def_components = list(/obj/item/stock_parts/cell = /obj/item/stock_parts/cell/high) - -/obj/item/circuitboard/machine/recycler - name = "Recycler (Machine Board)" - build_path = /obj/machinery/recycler - req_components = list( - /obj/item/stock_parts/matter_bin = 1, - /obj/item/stock_parts/manipulator = 1) - needs_anchored = FALSE - -/obj/item/circuitboard/machine/space_heater - name = "Space Heater (Machine Board)" - build_path = /obj/machinery/space_heater - req_components = list( - /obj/item/stock_parts/micro_laser = 1, - /obj/item/stock_parts/capacitor = 1, - /obj/item/stack/cable_coil = 3) - needs_anchored = FALSE /obj/item/circuitboard/machine/telecomms/broadcaster name = "Subspace Broadcaster (Machine Board)" + icon_state = "engineering" build_path = /obj/machinery/telecomms/broadcaster req_components = list( /obj/item/stock_parts/manipulator = 2, @@ -176,6 +132,7 @@ /obj/item/circuitboard/machine/telecomms/bus name = "Bus Mainframe (Machine Board)" + icon_state = "engineering" build_path = /obj/machinery/telecomms/bus req_components = list( /obj/item/stock_parts/manipulator = 2, @@ -184,14 +141,25 @@ /obj/item/circuitboard/machine/telecomms/hub name = "Hub Mainframe (Machine Board)" + icon_state = "engineering" build_path = /obj/machinery/telecomms/hub req_components = list( /obj/item/stock_parts/manipulator = 2, /obj/item/stack/cable_coil = 2, /obj/item/stock_parts/subspace/filter = 2) +/obj/item/circuitboard/machine/telecomms/message_server + name = "Messaging Server (Machine Board)" + icon_state = "engineering" + build_path = /obj/machinery/telecomms/message_server + req_components = list( + /obj/item/stock_parts/manipulator = 2, + /obj/item/stack/cable_coil = 1, + /obj/item/stock_parts/subspace/filter = 3) + /obj/item/circuitboard/machine/telecomms/processor name = "Processor Unit (Machine Board)" + icon_state = "engineering" build_path = /obj/machinery/telecomms/processor req_components = list( /obj/item/stock_parts/manipulator = 3, @@ -203,6 +171,7 @@ /obj/item/circuitboard/machine/telecomms/receiver name = "Subspace Receiver (Machine Board)" + icon_state = "engineering" build_path = /obj/machinery/telecomms/receiver req_components = list( /obj/item/stock_parts/subspace/ansible = 1, @@ -212,6 +181,7 @@ /obj/item/circuitboard/machine/telecomms/relay name = "Relay Mainframe (Machine Board)" + icon_state = "engineering" build_path = /obj/machinery/telecomms/relay req_components = list( /obj/item/stock_parts/manipulator = 2, @@ -220,133 +190,149 @@ /obj/item/circuitboard/machine/telecomms/server name = "Telecommunication Server (Machine Board)" + icon_state = "engineering" build_path = /obj/machinery/telecomms/server req_components = list( /obj/item/stock_parts/manipulator = 2, /obj/item/stack/cable_coil = 1, /obj/item/stock_parts/subspace/filter = 1) -/obj/item/circuitboard/machine/teleporter_hub - name = "Teleporter Hub (Machine Board)" - build_path = /obj/machinery/teleport/hub - req_components = list( - /obj/item/stack/ore/bluespace_crystal = 3, - /obj/item/stock_parts/matter_bin = 1) - def_components = list(/obj/item/stack/ore/bluespace_crystal = /obj/item/stack/ore/bluespace_crystal/artificial) +/obj/item/circuitboard/machine/tesla_coil + name = "Tesla Controller (Machine Board)" + icon_state = "engineering" + desc = "You can use a screwdriver to switch between Research and Power Generation." + build_path = /obj/machinery/power/tesla_coil + req_components = list(/obj/item/stock_parts/capacitor = 1) + needs_anchored = FALSE -/obj/item/circuitboard/machine/teleporter_station - name = "Teleporter Station (Machine Board)" - build_path = /obj/machinery/teleport/station - req_components = list( - /obj/item/stack/ore/bluespace_crystal = 2, - /obj/item/stock_parts/capacitor = 2, - /obj/item/stack/sheet/glass = 1) - def_components = list(/obj/item/stack/ore/bluespace_crystal = /obj/item/stack/ore/bluespace_crystal/artificial) +/obj/item/circuitboard/machine/cell_charger + name = "Cell Charger (Machine Board)" + icon_state = "engineering" + build_path = /obj/machinery/cell_charger + req_components = list(/obj/item/stock_parts/capacitor = 1) + needs_anchored = FALSE -/obj/item/circuitboard/machine/colormate - name = "Colormate (Machine Board)" - build_path = /obj/machinery/gear_painter +/obj/item/circuitboard/machine/circulator + name = "Circulator/Heat Exchanger (Machine Board)" + icon_state = "engineering" + build_path = /obj/machinery/atmospherics/components/binary/circulator req_components = list() - def_components = list() -/obj/item/circuitboard/machine/vendor - name = "Custom Vendor (Machine Board)" - desc = "You can turn the \"brand selection\" dial using a screwdriver." - custom_premium_price = 100 - build_path = /obj/machinery/vending/custom - req_components = list(/obj/item/vending_refill/custom = 1) +/obj/item/circuitboard/machine/emitter + name = "Emitter (Machine Board)" + icon_state = "engineering" + build_path = /obj/machinery/power/emitter + req_components = list( + /obj/item/stock_parts/micro_laser = 1, + /obj/item/stock_parts/manipulator = 1) + needs_anchored = FALSE - var/static/list/vending_names_paths = list( - /obj/machinery/vending/boozeomat = "Booze-O-Mat", - /obj/machinery/vending/coffee = "Solar's Best Hot Drinks", - /obj/machinery/vending/snack = "Getmore Chocolate Corp", - /obj/machinery/vending/cola = "Robust Softdrinks", - /obj/machinery/vending/cigarette = "ShadyCigs Deluxe", - /obj/machinery/vending/games = "\improper Good Clean Fun", - /obj/machinery/vending/kink = "KinkMate", - /obj/machinery/vending/autodrobe = "AutoDrobe", - /obj/machinery/vending/assist = "\improper Vendomat", - /obj/machinery/vending/engivend = "\improper Engi-Vend", - /obj/machinery/vending/tool = "\improper YouTool", - /obj/machinery/vending/sustenance = "\improper Sustenance Vendor", - /obj/machinery/vending/dinnerware = "\improper Plasteel Chef's Dinnerware Vendor", - /obj/machinery/vending/cart = "\improper PTech", - /obj/machinery/vending/hydronutrients = "\improper NutriMax", - /obj/machinery/vending/hydroseeds = "\improper MegaSeed Servitor", - /obj/machinery/vending/wardrobe/sec_wardrobe = "SecDrobe", - /obj/machinery/vending/wardrobe/medi_wardrobe = "MediDrobe", - /obj/machinery/vending/wardrobe/engi_wardrobe = "EngiDrobe", - /obj/machinery/vending/wardrobe/atmos_wardrobe = "AtmosDrobe", - /obj/machinery/vending/wardrobe/cargo_wardrobe = "CargoDrobe", - /obj/machinery/vending/wardrobe/robo_wardrobe = "RoboDrobe", - /obj/machinery/vending/wardrobe/science_wardrobe = "SciDrobe", - /obj/machinery/vending/wardrobe/hydro_wardrobe = "HyDrobe", - /obj/machinery/vending/wardrobe/curator_wardrobe = "CuraDrobe", - /obj/machinery/vending/wardrobe/bar_wardrobe = "BarDrobe", - /obj/machinery/vending/wardrobe/chef_wardrobe = "ChefDrobe", - /obj/machinery/vending/wardrobe/jani_wardrobe = "JaniDrobe", - /obj/machinery/vending/wardrobe/law_wardrobe = "LawDrobe", - /obj/machinery/vending/wardrobe/chap_wardrobe = "ChapDrobe", - /obj/machinery/vending/wardrobe/chem_wardrobe = "ChemDrobe", - /obj/machinery/vending/wardrobe/gene_wardrobe = "GeneDrobe", - /obj/machinery/vending/wardrobe/viro_wardrobe = "ViroDrobe", - /obj/machinery/vending/clothing = "ClothesMate", - /obj/machinery/vending/medical = "NanoMed Plus", - /obj/machinery/vending/wallmed = "NanoMed", - /obj/machinery/vending/custom = "Custom Vendor") +/obj/item/circuitboard/machine/generator + name = "Thermo-Electric Generator (Machine Board)" + icon_state = "engineering" + build_path = /obj/machinery/power/generator + req_components = list() -/obj/item/circuitboard/machine/vendor/attackby(obj/item/I, mob/user, params) - if(I.tool_behaviour == TOOL_SCREWDRIVER) - var/position = vending_names_paths.Find(build_path) - position = (position == vending_names_paths.len) ? 1 : (position + 1) - var/typepath = vending_names_paths[position] - set_type(typepath) - to_chat(user, "You set the board to \"[vending_names_paths[typepath]]\".") - else - return ..() - -/obj/item/circuitboard/machine/vendor/proc/set_type(obj/machinery/vending/typepath) - build_path = typepath - name = "[vending_names_paths[build_path]] Vendor (Machine Board)" - req_components = list(initial(typepath.refill_canister) = 1) - -/obj/item/circuitboard/machine/vendor/apply_default_parts(obj/machinery/M) - for(var/typepath in vending_names_paths) - if(istype(M, typepath)) - set_type(typepath) - break - return ..() - -/obj/item/circuitboard/machine/mech_recharger - name = "Mechbay Recharger (Machine Board)" - build_path = /obj/machinery/mech_bay_recharge_port +/obj/item/circuitboard/machine/ntnet_relay + name = "NTNet Relay (Machine Board)" + icon_state = "engineering" + build_path = /obj/machinery/ntnet_relay req_components = list( /obj/item/stack/cable_coil = 2, - /obj/item/stock_parts/capacitor = 5) + /obj/item/stock_parts/subspace/filter = 1) -/obj/item/circuitboard/machine/mechfab - name = "Exosuit Fabricator (Machine Board)" - build_path = /obj/machinery/mecha_part_fabricator - req_components = list( - /obj/item/stock_parts/matter_bin = 2, - /obj/item/stock_parts/manipulator = 1, - /obj/item/stock_parts/micro_laser = 1, - /obj/item/stack/sheet/glass = 1) - var/offstation_security_levels = TRUE - -/obj/item/circuitboard/machine/mechfab/offstation - offstation_security_levels = FALSE - -/obj/item/circuitboard/machine/mechfab/rnd_crafted(obj/machinery/rnd/production/P) - offstation_security_levels = P.offstation_security_levels - -/obj/item/circuitboard/machine/cryo_tube - name = "Cryotube (Machine Board)" - build_path = /obj/machinery/atmospherics/components/unary/cryo_cell +/obj/item/circuitboard/machine/pacman + name = "PACMAN-type Generator (Machine Board)" + icon_state = "engineering" + build_path = /obj/machinery/power/port_gen/pacman req_components = list( /obj/item/stock_parts/matter_bin = 1, - /obj/item/stack/cable_coil = 1, - /obj/item/stack/sheet/glass = 4) + /obj/item/stock_parts/micro_laser = 1, + /obj/item/stack/cable_coil = 2, + /obj/item/stock_parts/capacitor = 1) + needs_anchored = FALSE + +/obj/item/circuitboard/machine/pacman/super + name = "SUPERPACMAN-type Generator (Machine Board)" + icon_state = "engineering" + build_path = /obj/machinery/power/port_gen/pacman/super + +/obj/item/circuitboard/machine/pacman/mrs + name = "MRSPACMAN-type Generator (Machine Board)" + build_path = /obj/machinery/power/port_gen/pacman/mrs + +/obj/item/circuitboard/machine/power_compressor + name = "Power Compressor (Machine Board)" + icon_state = "engineering" + build_path = /obj/machinery/power/compressor + req_components = list( + /obj/item/stack/cable_coil = 5, + /obj/item/stock_parts/manipulator = 6) + +/obj/item/circuitboard/machine/power_turbine + name = "Power Turbine (Machine Board)" + icon_state = "engineering" + build_path = /obj/machinery/power/turbine + req_components = list( + /obj/item/stack/cable_coil = 5, + /obj/item/stock_parts/capacitor = 6) + +/obj/item/circuitboard/machine/protolathe/department/engineering + name = "Departmental Protolathe (Machine Board) - Engineering" + icon_state = "engineering" + build_path = /obj/machinery/rnd/production/protolathe/department/engineering + +/obj/item/circuitboard/machine/rad_collector + name = "Radiation Collector (Machine Board)" + icon_state = "engineering" + build_path = /obj/machinery/power/rad_collector + req_components = list( + /obj/item/stack/cable_coil = 5, + /obj/item/stock_parts/matter_bin = 1, + /obj/item/stack/sheet/plasmarglass = 2, + /obj/item/stock_parts/capacitor = 1, + /obj/item/stock_parts/manipulator = 1) + needs_anchored = FALSE + +/obj/item/circuitboard/machine/rtg + name = "RTG (Machine Board)" + icon_state = "engineering" + build_path = /obj/machinery/power/rtg + req_components = list( + /obj/item/stack/cable_coil = 5, + /obj/item/stock_parts/capacitor = 1, + /obj/item/stack/sheet/mineral/uranium = 10) // We have no Pu-238, and this is the closest thing to it. + +/obj/item/circuitboard/machine/rtg/advanced + name = "Advanced RTG (Machine Board)" + build_path = /obj/machinery/power/rtg/advanced + req_components = list( + /obj/item/stack/cable_coil = 5, + /obj/item/stock_parts/capacitor = 1, + /obj/item/stock_parts/micro_laser = 1, + /obj/item/stack/sheet/mineral/uranium = 10, + /obj/item/stack/sheet/mineral/plasma = 5) + +// /obj/item/circuitboard/machine/scanner_gate +// name = "Scanner Gate (Machine Board)" +// icon_state = "engineering" +// build_path = /obj/machinery/scanner_gate +// req_components = list( +// /obj/item/stock_parts/scanning_module = 3) + +/obj/item/circuitboard/machine/smes + name = "SMES (Machine Board)" + icon_state = "engineering" + build_path = /obj/machinery/power/smes + req_components = list( + /obj/item/stack/cable_coil = 5, + /obj/item/stock_parts/cell = 5, + /obj/item/stock_parts/capacitor = 1) + def_components = list(/obj/item/stock_parts/cell = /obj/item/stock_parts/cell/high/empty) + +/obj/item/circuitboard/machine/techfab/department/engineering + name = "\improper Departmental Techfab (Machine Board) - Engineering" + build_path = /obj/machinery/rnd/production/techfab/department/engineering /obj/item/circuitboard/machine/thermomachine name = "Thermomachine (Machine Board)" @@ -410,52 +396,129 @@ #undef PATH_FREEZER #undef PATH_HEATER -/obj/item/circuitboard/machine/deep_fryer - name = "circuit board (Deep Fryer)" - build_path = /obj/machinery/deepfryer - req_components = list(/obj/item/stock_parts/micro_laser = 1) - needs_anchored = FALSE +// /obj/item/circuitboard/machine/HFR_fuel_input +// name = "HFR Fuel Input (Machine Board)" +// icon_state = "engineering" +// build_path = /obj/machinery/atmospherics/components/unary/hypertorus/fuel_input +// req_components = list( +// /obj/item/stack/sheet/plasteel = 5) -/obj/item/circuitboard/machine/gibber - name = "Gibber (Machine Board)" - build_path = /obj/machinery/gibber +// /obj/item/circuitboard/machine/HFR_waste_output +// name = "HFR Waste Output (Machine Board)" +// icon_state = "engineering" +// build_path = /obj/machinery/atmospherics/components/unary/hypertorus/waste_output +// req_components = list( +// /obj/item/stack/sheet/plasteel = 5) + +// /obj/item/circuitboard/machine/HFR_moderator_input +// name = "HFR Moderator Input (Machine Board)" +// icon_state = "engineering" +// build_path = /obj/machinery/atmospherics/components/unary/hypertorus/moderator_input +// req_components = list( +// /obj/item/stack/sheet/plasteel = 5) + +// /obj/item/circuitboard/machine/HFR_core +// name = "HFR core (Machine Board)" +// icon_state = "engineering" +// build_path = /obj/machinery/atmospherics/components/unary/hypertorus/core +// req_components = list( +// /obj/item/stack/cable_coil = 10, +// /obj/item/stack/sheet/glass = 10, +// /obj/item/stack/sheet/plasteel = 10) + +// /obj/item/circuitboard/machine/HFR_corner +// name = "HFR Corner (Machine Board)" +// icon_state = "engineering" +// build_path = /obj/machinery/hypertorus/corner +// req_components = list( +// /obj/item/stack/sheet/plasteel = 5) + +// /obj/item/circuitboard/machine/HFR_interface +// name = "HFR Interface (Machine Board)" +// icon_state = "engineering" +// build_path = /obj/machinery/hypertorus/interface +// req_components = list( +// /obj/item/stack/cable_coil = 10, +// /obj/item/stack/sheet/glass = 10, +// /obj/item/stack/sheet/plasteel = 5) + +//Generic + +/obj/item/circuitboard/machine/circuit_imprinter + name = "Circuit Imprinter (Machine Board)" + icon_state = "generic" + build_path = /obj/machinery/rnd/production/circuit_imprinter req_components = list( /obj/item/stock_parts/matter_bin = 1, - /obj/item/stock_parts/manipulator = 1) - needs_anchored = FALSE + /obj/item/stock_parts/manipulator = 1, + /obj/item/reagent_containers/glass/beaker = 2) -/obj/item/circuitboard/machine/monkey_recycler - name = "Monkey Recycler (Machine Board)" - build_path = /obj/machinery/monkey_recycler - req_components = list( - /obj/item/stock_parts/matter_bin = 1, - /obj/item/stock_parts/manipulator = 1) - needs_anchored = FALSE +/obj/item/circuitboard/machine/circuit_imprinter/department + name = "Departmental Circuit Imprinter (Machine Board)" + icon_state = "generic" + build_path = /obj/machinery/rnd/production/circuit_imprinter/department -/obj/item/circuitboard/machine/processor - name = "Food Processor (Machine Board)" - build_path = /obj/machinery/processor - req_components = list( - /obj/item/stock_parts/matter_bin = 1, - /obj/item/stock_parts/manipulator = 1) - needs_anchored = FALSE +/obj/item/circuitboard/machine/holopad + name = "AI Holopad (Machine Board)" + icon_state = "generic" + build_path = /obj/machinery/holopad + req_components = list(/obj/item/stock_parts/capacitor = 1) + needs_anchored = FALSE //wew lad + var/secure = FALSE -/obj/item/circuitboard/machine/processor/attackby(obj/item/I, mob/user, params) - if(I.tool_behaviour == TOOL_SCREWDRIVER) - if(build_path == /obj/machinery/processor) - name = "Slime Processor (Machine Board)" - build_path = /obj/machinery/processor/slime - to_chat(user, "Name protocols successfully updated.") +/obj/item/circuitboard/machine/holopad/attackby(obj/item/P, mob/user, params) + if(P.tool_behaviour == TOOL_MULTITOOL) + if(secure) + build_path = /obj/machinery/holopad + secure = FALSE else - name = "Food Processor (Machine Board)" - build_path = /obj/machinery/processor - to_chat(user, "Defaulting name protocols.") - else - return ..() + build_path = /obj/machinery/holopad //secure + secure = TRUE + to_chat(user, "You [secure? "en" : "dis"]able the security on the [src]") + . = ..() -/obj/item/circuitboard/machine/processor/slime - name = "Slime Processor (Machine Board)" - build_path = /obj/machinery/processor/slime +/obj/item/circuitboard/machine/holopad/examine(mob/user) + . = ..() + . += "There is a connection port on this board that could be pulsed" + if(secure) + . += "There is a red light flashing next to the word \"secure\"" + +/obj/item/circuitboard/machine/launchpad + name = "Bluespace Launchpad (Machine Board)" + icon_state = "generic" + build_path = /obj/machinery/launchpad + req_components = list( + /obj/item/stack/ore/bluespace_crystal = 1, + /obj/item/stock_parts/manipulator = 1) + def_components = list(/obj/item/stack/ore/bluespace_crystal = /obj/item/stack/ore/bluespace_crystal/artificial) + +/obj/item/circuitboard/machine/paystand + name = "Pay Stand (Machine Board)" + icon_state = "generic" + build_path = /obj/machinery/paystand + req_components = list() + +/obj/item/circuitboard/machine/protolathe + name = "Protolathe (Machine Board)" + icon_state = "generic" + build_path = /obj/machinery/rnd/production/protolathe + req_components = list( + /obj/item/stock_parts/matter_bin = 2, + /obj/item/stock_parts/manipulator = 2, + /obj/item/reagent_containers/glass/beaker = 2) + +/obj/item/circuitboard/machine/protolathe/department + name = "Departmental Protolathe (Machine Board)" + icon_state = "generic" + build_path = /obj/machinery/rnd/production/protolathe/department + +/obj/item/circuitboard/machine/reagentgrinder + name = "Machine Design (All-In-One Grinder)" + icon_state = "generic" + build_path = /obj/machinery/reagentgrinder/constructed + req_components = list( + /obj/item/stock_parts/manipulator = 1) + needs_anchored = FALSE /obj/item/circuitboard/machine/smartfridge name = "Smartfridge (Machine Board)" @@ -470,14 +533,17 @@ /obj/machinery/smartfridge/chemistry/virology = "viruses", /obj/machinery/smartfridge/disks = "disks") needs_anchored = FALSE + var/is_special_type = FALSE -/obj/item/circuitboard/machine/smartfridge/Initialize(mapload, new_type) - if(new_type) - build_path = new_type +/obj/item/circuitboard/machine/smartfridge/apply_default_parts(obj/machinery/smartfridge/M) + build_path = M.base_build_path + if(!fridges_name_paths.Find(build_path, fridges_name_paths)) + name = "[initial(M.name)] (Machine Board)" //if it's a unique type, give it a unique name. + is_special_type = TRUE return ..() /obj/item/circuitboard/machine/smartfridge/attackby(obj/item/I, mob/user, params) - if(I.tool_behaviour == TOOL_SCREWDRIVER) + if(!is_special_type && I.tool_behaviour == TOOL_SCREWDRIVER) var/position = fridges_name_paths.Find(build_path, fridges_name_paths) position = (position == fridges_name_paths.len) ? 1 : (position + 1) build_path = fridges_name_paths[position] @@ -487,227 +553,167 @@ /obj/item/circuitboard/machine/smartfridge/examine(mob/user) . = ..() + if(is_special_type) + return . += "[src] is set to [fridges_name_paths[build_path]]. You can use a screwdriver to reconfigure it." -/obj/item/circuitboard/machine/biogenerator - name = "Biogenerator (Machine Board)" - build_path = /obj/machinery/biogenerator - req_components = list( - /obj/item/stock_parts/matter_bin = 1, - /obj/item/stock_parts/manipulator = 1, - /obj/item/stack/cable_coil = 1, - /obj/item/stack/sheet/glass = 1) -/obj/item/circuitboard/machine/plantgenes - name = "Plant DNA Manipulator (Machine Board)" - build_path = /obj/machinery/plantgenes +/obj/item/circuitboard/machine/space_heater + name = "Space Heater (Machine Board)" + icon_state = "generic" + build_path = /obj/machinery/space_heater req_components = list( - /obj/item/stock_parts/manipulator = 1, /obj/item/stock_parts/micro_laser = 1, - /obj/item/stack/sheet/glass = 1, - /obj/item/stock_parts/scanning_module = 1) + /obj/item/stock_parts/capacitor = 1, + /obj/item/stack/cable_coil = 3) + needs_anchored = FALSE -/obj/item/circuitboard/machine/plantgenes/vault - name = "alien board (Plant DNA Manipulator)" - icon_state = "abductor_mod" - // It wasn't made by actual abductors race, so no abductor tech here. - def_components = list( - /obj/item/stock_parts/manipulator = /obj/item/stock_parts/manipulator/femto, - /obj/item/stock_parts/micro_laser = /obj/item/stock_parts/micro_laser/quadultra, - /obj/item/stock_parts/scanning_module = /obj/item/stock_parts/scanning_module/triphasic) +// /obj/item/circuitboard/machine/electrolyzer +// name = "Electrolyzer (Machine Board)" +// icon_state = "generic" +// build_path = /obj/machinery/electrolyzer +// req_components = list( +// /obj/item/stock_parts/electrolite = 2, +// /obj/item/stock_parts/capacitor = 2, +// /obj/item/stack/cable_coil = 5, +// /obj/item/stack/sheet/glass = 1) + +// needs_anchored = FALSE -/obj/item/circuitboard/machine/hydroponics - name = "Hydroponics Tray (Machine Board)" - build_path = /obj/machinery/hydroponics/constructable +/obj/item/circuitboard/machine/techfab + name = "\improper Techfab (Machine Board)" + icon_state = "generic" + build_path = /obj/machinery/rnd/production/techfab req_components = list( /obj/item/stock_parts/matter_bin = 2, - /obj/item/stock_parts/manipulator = 1, - /obj/item/stack/sheet/glass = 1) - needs_anchored = FALSE + /obj/item/stock_parts/manipulator = 2, + /obj/item/reagent_containers/glass/beaker = 2) -/obj/item/circuitboard/machine/hydroponics/automagic - name = "Automatic Hydroponics Tray (Machine Board)" - build_path = /obj/machinery/hydroponics/constructable/automagic +/obj/item/circuitboard/machine/techfab/department + name = "\improper Departmental Techfab (Machine Board)" + build_path = /obj/machinery/rnd/production/techfab/department -/obj/item/circuitboard/machine/seed_extractor - name = "Seed Extractor (Machine Board)" - build_path = /obj/machinery/seed_extractor - req_components = list( - /obj/item/stock_parts/matter_bin = 1, - /obj/item/stock_parts/manipulator = 1) - needs_anchored = FALSE +/obj/item/circuitboard/machine/vendor + name = "Custom Vendor (Machine Board)" + desc = "You can turn the \"brand selection\" dial using a screwdriver." + custom_premium_price = PAYCHECK_ASSISTANT * 1.5 + build_path = /obj/machinery/vending/custom + req_components = list(/obj/item/vending_refill/custom = 1) -/obj/item/circuitboard/machine/ore_redemption - name = "Ore Redemption (Machine Board)" - build_path = /obj/machinery/mineral/ore_redemption - req_components = list( - /obj/item/stack/sheet/glass = 1, - /obj/item/stock_parts/matter_bin = 1, - /obj/item/stock_parts/micro_laser = 1, - /obj/item/stock_parts/manipulator = 1, - /obj/item/assembly/igniter = 1) - needs_anchored = FALSE + var/static/list/vending_names_paths = list( + /obj/machinery/vending/boozeomat = "Booze-O-Mat", + /obj/machinery/vending/coffee = "Solar's Best Hot Drinks", + /obj/machinery/vending/snack = "Getmore Chocolate Corp", + /obj/machinery/vending/cola = "Robust Softdrinks", + /obj/machinery/vending/cigarette = "ShadyCigs Deluxe", + /obj/machinery/vending/games = "\improper Good Clean Fun", + /obj/machinery/vending/kink = "KinkMate", + /obj/machinery/vending/autodrobe = "AutoDrobe", + /obj/machinery/vending/wardrobe/sec_wardrobe = "SecDrobe", + /obj/machinery/vending/wardrobe/det_wardrobe = "DetDrobe", + /obj/machinery/vending/wardrobe/medi_wardrobe = "MediDrobe", + /obj/machinery/vending/wardrobe/engi_wardrobe = "EngiDrobe", + /obj/machinery/vending/wardrobe/atmos_wardrobe = "AtmosDrobe", + /obj/machinery/vending/wardrobe/cargo_wardrobe = "CargoDrobe", + /obj/machinery/vending/wardrobe/robo_wardrobe = "RoboDrobe", + /obj/machinery/vending/wardrobe/science_wardrobe = "SciDrobe", + /obj/machinery/vending/wardrobe/hydro_wardrobe = "HyDrobe", + /obj/machinery/vending/wardrobe/curator_wardrobe = "CuraDrobe", + /obj/machinery/vending/wardrobe/bar_wardrobe = "BarDrobe", + /obj/machinery/vending/wardrobe/chef_wardrobe = "ChefDrobe", + /obj/machinery/vending/wardrobe/jani_wardrobe = "JaniDrobe", + /obj/machinery/vending/wardrobe/law_wardrobe = "LawDrobe", + /obj/machinery/vending/wardrobe/chap_wardrobe = "ChapDrobe", + /obj/machinery/vending/wardrobe/chem_wardrobe = "ChemDrobe", + /obj/machinery/vending/wardrobe/gene_wardrobe = "GeneDrobe", + /obj/machinery/vending/wardrobe/viro_wardrobe = "ViroDrobe", + /obj/machinery/vending/clothing = "ClothesMate", + /obj/machinery/vending/medical = "NanoMed Plus", + /obj/machinery/vending/wallmed = "NanoMed", + /obj/machinery/vending/assist = "Vendomat", + /obj/machinery/vending/engivend = "Engi-Vend", + /obj/machinery/vending/tool = "YouTool", + /obj/machinery/vending/hydronutrients = "NutriMax", + /obj/machinery/vending/hydroseeds = "MegaSeed Servitor", + /obj/machinery/vending/sustenance = "Sustenance Vendor", + /obj/machinery/vending/dinnerware = "Plasteel Chef's Dinnerware Vendor", + /obj/machinery/vending/cart = "PTech", + /obj/machinery/vending/robotics = "Robotech Deluxe", + /obj/machinery/vending/engineering = "Robco Tool Maker", + /obj/machinery/vending/sovietsoda = "BODA", + /obj/machinery/vending/security = "SecTech", + // /obj/machinery/vending/modularpc = "Deluxe Silicate Selections", + /obj/machinery/vending/custom = "Custom Vendor") -/obj/item/circuitboard/machine/mining_equipment_vendor - name = "Mining Equipment Vendor (Machine Board)" - build_path = /obj/machinery/mineral/equipment_vendor - req_components = list( - /obj/item/stack/sheet/glass = 1, - /obj/item/stock_parts/matter_bin = 3) - -/obj/item/circuitboard/machine/mining_equipment_vendor/golem - name = "Golem Ship Equipment Vendor (Machine Board)" - build_path = /obj/machinery/mineral/equipment_vendor/golem - -/obj/item/circuitboard/machine/ntnet_relay - name = "NTNet Relay (Machine Board)" - build_path = /obj/machinery/ntnet_relay - req_components = list( - /obj/item/stack/cable_coil = 2, - /obj/item/stock_parts/subspace/filter = 1) - -/obj/item/circuitboard/machine/pacman - name = "PACMAN-type Generator (Machine Board)" - build_path = /obj/machinery/power/port_gen/pacman - req_components = list( - /obj/item/stock_parts/matter_bin = 1, - /obj/item/stock_parts/micro_laser = 1, - /obj/item/stack/cable_coil = 2, - /obj/item/stock_parts/capacitor = 1) - needs_anchored = FALSE - -/obj/item/circuitboard/machine/pacman/super - name = "SUPERPACMAN-type Generator (Machine Board)" - build_path = /obj/machinery/power/port_gen/pacman/super - -/obj/item/circuitboard/machine/pacman/mrs - name = "MRSPACMAN-type Generator (Machine Board)" - build_path = /obj/machinery/power/port_gen/pacman/mrs - -/obj/item/circuitboard/machine/rtg - name = "RTG (Machine Board)" - build_path = /obj/machinery/power/rtg - req_components = list( - /obj/item/stack/cable_coil = 5, - /obj/item/stock_parts/capacitor = 1, - /obj/item/stack/sheet/mineral/uranium = 10) // We have no Pu-238, and this is the closest thing to it. - -/obj/item/circuitboard/machine/rtg/advanced - name = "Advanced RTG (Machine Board)" - build_path = /obj/machinery/power/rtg/advanced - req_components = list( - /obj/item/stack/cable_coil = 5, - /obj/item/stock_parts/capacitor = 1, - /obj/item/stock_parts/micro_laser = 1, - /obj/item/stack/sheet/mineral/uranium = 10, - /obj/item/stack/sheet/mineral/plasma = 5) - -/obj/item/circuitboard/machine/abductor/core - name = "alien board (Void Core)" - build_path = /obj/machinery/power/rtg/abductor - req_components = list( - /obj/item/stock_parts/capacitor = 1, - /obj/item/stock_parts/micro_laser = 1, - /obj/item/stock_parts/cell/infinite/abductor = 1) - def_components = list( - /obj/item/stock_parts/capacitor = /obj/item/stock_parts/capacitor/quadratic, - /obj/item/stock_parts/micro_laser = /obj/item/stock_parts/micro_laser/quadultra) - -/obj/item/circuitboard/machine/emitter - name = "Emitter (Machine Board)" - build_path = /obj/machinery/power/emitter - req_components = list( - /obj/item/stock_parts/micro_laser = 1, - /obj/item/stock_parts/manipulator = 1) - needs_anchored = FALSE - -/obj/item/circuitboard/machine/smes - name = "SMES (Machine Board)" - build_path = /obj/machinery/power/smes - req_components = list( - /obj/item/stack/cable_coil = 5, - /obj/item/stock_parts/cell = 5, - /obj/item/stock_parts/capacitor = 1) - def_components = list(/obj/item/stock_parts/cell = /obj/item/stock_parts/cell/high/empty) - -/obj/item/circuitboard/machine/rad_collector - name = "Radiation Collector (Machine Board)" - build_path = /obj/machinery/power/rad_collector - req_components = list( - /obj/item/stack/cable_coil = 5, - /obj/item/stock_parts/matter_bin = 1, - /obj/item/stack/sheet/plasmarglass = 2, - /obj/item/stock_parts/capacitor = 1, - /obj/item/stock_parts/manipulator = 1) - needs_anchored = FALSE - -/obj/item/circuitboard/machine/tesla_coil - name = "Tesla Controller (Machine Board)" - desc = "You can use a screwdriver to switch between Research and Power Generation." - build_path = /obj/machinery/power/tesla_coil - req_components = list(/obj/item/stock_parts/capacitor = 1) - needs_anchored = FALSE - -#define PATH_POWERCOIL /obj/machinery/power/tesla_coil/power -#define PATH_RPCOIL /obj/machinery/power/tesla_coil/research - -/obj/item/circuitboard/machine/tesla_coil/Initialize() - . = ..() - if(build_path) - build_path = PATH_POWERCOIL - -/obj/item/circuitboard/machine/tesla_coil/attackby(obj/item/I, mob/user, params) +/obj/item/circuitboard/machine/vendor/attackby(obj/item/I, mob/user, params) if(I.tool_behaviour == TOOL_SCREWDRIVER) - var/obj/item/circuitboard/new_type - var/new_setting - switch(build_path) - if(PATH_POWERCOIL) - new_type = /obj/item/circuitboard/machine/tesla_coil/research - new_setting = "Research" - if(PATH_RPCOIL) - new_type = /obj/item/circuitboard/machine/tesla_coil/power - new_setting = "Power" - name = initial(new_type.name) - build_path = initial(new_type.build_path) - I.play_tool_sound(src) - to_chat(user, "You change the circuitboard setting to \"[new_setting]\".") + var/static/list/display_vending_names_paths + if(!display_vending_names_paths) + display_vending_names_paths = list() + for(var/path in vending_names_paths) + display_vending_names_paths[vending_names_paths[path]] = path + var/choice = input(user,"Choose a new brand","Select an Item") as null|anything in sortList(display_vending_names_paths) + set_type(display_vending_names_paths[choice]) else return ..() -/obj/item/circuitboard/machine/tesla_coil/power - name = "Tesla Coil (Machine Board)" - build_path = PATH_POWERCOIL +/obj/item/circuitboard/machine/vendor/proc/set_type(obj/machinery/vending/typepath) + build_path = typepath + name = "[vending_names_paths[build_path]] Vendor (Machine Board)" + req_components = list(initial(typepath.refill_canister) = 1) -/obj/item/circuitboard/machine/tesla_coil/research - name = "Tesla Corona Researcher (Machine Board)" - build_path = PATH_RPCOIL +/obj/item/circuitboard/machine/vendor/apply_default_parts(obj/machinery/M) + for(var/typepath in vending_names_paths) + if(istype(M, typepath)) + set_type(typepath) + break + return ..() -#undef PATH_POWERCOIL -#undef PATH_RPCOIL - -/obj/item/circuitboard/machine/grounding_rod - name = "Grounding Rod (Machine Board)" - build_path = /obj/machinery/power/grounding_rod - req_components = list(/obj/item/stock_parts/capacitor = 1) - needs_anchored = FALSE - -/obj/item/circuitboard/machine/power_compressor - name = "Power Compressor (Machine Board)" - build_path = /obj/machinery/power/compressor +/obj/item/circuitboard/machine/vending/donksofttoyvendor + name = "Donksoft Toy Vendor (Machine Board)" + build_path = /obj/machinery/vending/donksofttoyvendor req_components = list( - /obj/item/stack/cable_coil = 5, - /obj/item/stock_parts/manipulator = 6) + /obj/item/stack/sheet/glass = 1, + /obj/item/vending_refill/donksoft = 1) -/obj/item/circuitboard/machine/power_turbine - name = "Power Turbine (Machine Board)" - build_path = /obj/machinery/power/turbine +/obj/item/circuitboard/machine/vending/syndicatedonksofttoyvendor + name = "Syndicate Donksoft Toy Vendor (Machine Board)" + build_path = /obj/machinery/vending/toyliberationstation req_components = list( - /obj/item/stack/cable_coil = 5, - /obj/item/stock_parts/capacitor = 6) + /obj/item/stack/sheet/glass = 1, + /obj/item/vending_refill/donksoft = 1) + +// /obj/item/circuitboard/machine/bountypad +// name = "Civilian Bounty Pad (Machine Board)" +// icon_state = "generic" +// build_path = /obj/machinery/piratepad/civilian +// req_components = list( +// /obj/item/stock_parts/card_reader = 1, +// /obj/item/stock_parts/scanning_module = 1, +// /obj/item/stock_parts/micro_laser = 1 +// ) + +// /obj/item/circuitboard/machine/accounting +// name = "Account Registration Device (Machine Board)" +// icon_state = "command" +// build_path = /obj/machinery/accounting +// req_components = list( +// /obj/item/stock_parts/card_reader = 1, +// /obj/item/stock_parts/scanning_module = 1 +// ) + +/obj/item/circuitboard/machine/colormate + name = "Colormate (Machine Board)" + icon_state = "generic" + build_path = /obj/machinery/gear_painter + req_components = list() + +//Medical /obj/item/circuitboard/machine/chem_dispenser name = "Chem Dispenser (Machine Board)" + icon_state = "medical" build_path = /obj/machinery/chem_dispenser req_components = list( /obj/item/stock_parts/matter_bin = 2, @@ -729,38 +735,16 @@ /obj/item/stock_parts/cell = 1) def_components = list(/obj/item/stock_parts/cell = /obj/item/stock_parts/cell/upgraded/plus) -/obj/item/circuitboard/machine/chem_dispenser/drinks - name = "Soda Dispenser (Machine Board)" - build_path = /obj/machinery/chem_dispenser/drinks - -/obj/item/circuitboard/machine/chem_dispenser/drinks/beer - name = "Booze Dispenser (Machine Board)" - build_path = /obj/machinery/chem_dispenser/drinks/beer - /obj/item/circuitboard/machine/chem_dispenser/abductor - name = "Reagent Synthetizer (Abductor Machine Board)" + name = "Reagent Synthesizer (Abductor Machine Board)" icon_state = "abductor_mod" build_path = /obj/machinery/chem_dispenser/abductor def_components = list(/obj/item/stock_parts/cell = /obj/item/stock_parts/cell/high) needs_anchored = FALSE -/obj/item/circuitboard/machine/sleeper/party - name = "Party Pod (Machine Board)" - build_path = /obj/machinery/sleeper/party - -/obj/item/circuitboard/machine/smoke_machine - name = "Smoke Machine (Machine Board)" - build_path = /obj/machinery/smoke_machine - req_components = list( - /obj/item/stock_parts/matter_bin = 2, - /obj/item/stock_parts/capacitor = 1, - /obj/item/stock_parts/manipulator = 1, - /obj/item/stack/sheet/glass = 1, - /obj/item/stock_parts/cell = 1) - needs_anchored = FALSE - /obj/item/circuitboard/machine/chem_heater name = "Chemical Heater (Machine Board)" + icon_state = "medical" build_path = /obj/machinery/chem_heater req_components = list( /obj/item/stock_parts/micro_laser = 1, @@ -768,6 +752,7 @@ /obj/item/circuitboard/machine/chem_master name = "ChemMaster 3000 (Machine Board)" + icon_state = "medical" build_path = /obj/machinery/chem_master desc = "You can turn the \"mode selection\" dial using a screwdriver." req_components = list( @@ -791,42 +776,190 @@ else return ..() -/obj/item/circuitboard/machine/reagentgrinder - name = "Machine Design (All-In-One Grinder)" - build_path = /obj/machinery/reagentgrinder/constructed - req_components = list( - /obj/item/stock_parts/manipulator = 1) - needs_anchored = FALSE +/obj/item/circuitboard/machine/autobottler + name = "Auto-Bottler (Machine Board)" + icon_state = "medical" + build_path = /obj/machinery/rnd/production/protolathe/department/autobottler //Manips make you print things cheaper, even chems + req_components = list(/obj/item/stock_parts/matter_bin = 5, + /obj/item/stack/sheet/glass = 2, + /obj/item/stock_parts/capacitor = 1, + /obj/item/stack/cable_coil = 5, + /obj/item/reagent_containers/glass/beaker = 6) //So it can hold lots of chems -/obj/item/circuitboard/machine/chem_master/condi - name = "CondiMaster 3000 (Machine Board)" - build_path = /obj/machinery/chem_master/condimaster - -/obj/item/circuitboard/machine/circuit_imprinter - name = "Circuit Imprinter (Machine Board)" - build_path = /obj/machinery/rnd/production/circuit_imprinter +/obj/item/circuitboard/machine/bloodbankgen + name = "Blood Bank Generator (Machine Board)" + icon_state = "medical" + build_path = /obj/machinery/bloodbankgen req_components = list( /obj/item/stock_parts/matter_bin = 1, /obj/item/stock_parts/manipulator = 1, - /obj/item/reagent_containers/glass/beaker = 2) - var/offstation_security_levels = TRUE + /obj/item/stack/cable_coil = 5, + /obj/item/stack/sheet/glass = 1) -/obj/item/circuitboard/machine/circuit_imprinter/offstation - offstation_security_levels = FALSE +/obj/item/circuitboard/machine/clonescanner + name = "Cloning Scanner (Machine Board)" + icon_state = "medical" + build_path = /obj/machinery/dna_scannernew + req_components = list( + /obj/item/stock_parts/scanning_module = 1, + /obj/item/stock_parts/matter_bin = 1, + /obj/item/stock_parts/micro_laser = 1, + /obj/item/stack/sheet/glass = 1, + /obj/item/stack/cable_coil = 2) -/obj/item/circuitboard/machine/mechfab/rnd_crafted(obj/machinery/rnd/production/P) - offstation_security_levels = P.offstation_security_levels +/obj/item/circuitboard/machine/clonepod + name = "Clone Pod (Machine Board)" + icon_state = "medical" + build_path = /obj/machinery/clonepod + req_components = list( + /obj/item/stack/cable_coil = 2, + /obj/item/stock_parts/scanning_module = 2, + /obj/item/stock_parts/manipulator = 2, + /obj/item/stack/sheet/glass = 1) -/obj/item/circuitboard/machine/circuit_imprinter/department - name = "Departmental Circuit Imprinter (Machine Board)" - build_path = /obj/machinery/rnd/production/circuit_imprinter/department +/obj/item/circuitboard/machine/clonepod/experimental + name = "Experimental Clone Pod (Machine Board)" + icon_state = "medical" + build_path = /obj/machinery/clonepod/experimental + +/obj/item/circuitboard/machine/cryo_tube + name = "Cryotube (Machine Board)" + icon_state = "medical" + build_path = /obj/machinery/atmospherics/components/unary/cryo_cell + req_components = list( + /obj/item/stock_parts/matter_bin = 1, + /obj/item/stack/cable_coil = 1, + /obj/item/stack/sheet/glass = 4) + +// /obj/item/circuitboard/machine/fat_sucker +// name = "Lipid Extractor (Machine Board)" +// icon_state = "medical" +// build_path = /obj/machinery/fat_sucker +// req_components = list(/obj/item/stock_parts/micro_laser = 1, +// /obj/item/kitchen/fork = 1) + +/obj/item/circuitboard/machine/harvester + name = "Harvester (Machine Board)" + icon_state = "medical" + build_path = /obj/machinery/harvester + req_components = list(/obj/item/stock_parts/micro_laser = 4) + +// /obj/item/circuitboard/machine/medical_kiosk +// name = "Medical Kiosk (Machine Board)" +// icon_state = "medical" +// build_path = /obj/machinery/medical_kiosk +// var/custom_cost = 10 +// req_components = list( +// /obj/item/healthanalyzer = 1, +// /obj/item/stock_parts/scanning_module = 1) + +// /obj/item/circuitboard/machine/medical_kiosk/multitool_act(mob/living/user) +// . = ..() +// var/new_cost = input("Set a new cost for using this medical kiosk.","New cost", custom_cost) as num|null +// if(!new_cost || (loc != user)) +// to_chat(user, "You must hold the circuitboard to change its cost!") +// return +// custom_cost = clamp(round(new_cost, 1), 10, 1000) +// to_chat(user, "The cost is now set to [custom_cost].") + +// /obj/item/circuitboard/machine/medical_kiosk/examine(mob/user) +// . = ..() +// . += "The cost to use this kiosk is set to [custom_cost]." + +/obj/item/circuitboard/machine/limbgrower + name = "Limb Grower (Machine Board)" + icon_state = "medical" + build_path = /obj/machinery/limbgrower + req_components = list( + /obj/item/stock_parts/manipulator = 1, + /obj/item/reagent_containers/glass/beaker = 2, + /obj/item/stack/sheet/glass = 1) + +/obj/item/circuitboard/machine/protolathe/department/medical + name = "Departmental Protolathe (Machine Board) - Medical" + icon_state = "medical" + build_path = /obj/machinery/rnd/production/protolathe/department/medical + +/obj/item/circuitboard/machine/sleeper + name = "Sleeper (Machine Board)" + icon_state = "medical" + build_path = /obj/machinery/sleeper + req_components = list( + /obj/item/stock_parts/matter_bin = 1, + /obj/item/stock_parts/manipulator = 1, + /obj/item/stack/cable_coil = 1, + /obj/item/stack/sheet/glass = 2) + +/obj/item/circuitboard/machine/sleeper/party + name = "Party Pod (Machine Board)" + build_path = /obj/machinery/sleeper/party + +/obj/item/circuitboard/machine/sleeper/syndie + name = "Syndicate Sleeper (Machine Board)" + build_path = /obj/machinery/sleeper/syndie + +/obj/item/circuitboard/machine/vr_sleeper + name = "VR Sleeper (Machine Board)" + build_path = /obj/machinery/vr_sleeper + req_components = list( + /obj/item/stock_parts/manipulator = 1, + /obj/item/stack/cable_coil = 1, + /obj/item/stock_parts/scanning_module = 2, + /obj/item/stack/sheet/glass = 2) + +/obj/item/circuitboard/machine/smoke_machine + name = "Smoke Machine (Machine Board)" + icon_state = "medical" + build_path = /obj/machinery/smoke_machine + req_components = list( + /obj/item/stock_parts/matter_bin = 2, + /obj/item/stock_parts/capacitor = 1, + /obj/item/stock_parts/manipulator = 1, + /obj/item/stack/sheet/glass = 1, + /obj/item/stock_parts/cell = 1) + needs_anchored = FALSE + +// /obj/item/circuitboard/machine/stasis +// name = "\improper Lifeform Stasis Unit (Machine Board)" +// icon_state = "medical" +// build_path = /obj/machinery/stasis +// req_components = list( +// /obj/item/stack/cable_coil = 3, +// /obj/item/stock_parts/manipulator = 1, +// /obj/item/stock_parts/capacitor = 1) + +/obj/item/circuitboard/machine/medipen_refiller + name = "Medipen Refiller (Machine Board)" + icon_state = "medical" + build_path = /obj/machinery/medipen_refiller + req_components = list( + /obj/item/stock_parts/matter_bin = 1) + +/obj/item/circuitboard/machine/techfab/department/medical + name = "\improper Departmental Techfab (Machine Board) - Medical" + icon_state = "medical" + build_path = /obj/machinery/rnd/production/techfab/department/medical + +//Science /obj/item/circuitboard/machine/circuit_imprinter/department/science name = "Departmental Circuit Imprinter - Science (Machine Board)" + icon_state = "science" build_path = /obj/machinery/rnd/production/circuit_imprinter/department/science +/obj/item/circuitboard/machine/cyborgrecharger + name = "Cyborg Recharger (Machine Board)" + icon_state = "science" + build_path = /obj/machinery/recharge_station + req_components = list( + /obj/item/stock_parts/capacitor = 2, + /obj/item/stock_parts/cell = 1, + /obj/item/stock_parts/manipulator = 1) + def_components = list(/obj/item/stock_parts/cell = /obj/item/stock_parts/cell/high) + /obj/item/circuitboard/machine/destructive_analyzer name = "Destructive Analyzer (Machine Board)" + icon_state = "science" build_path = /obj/machinery/rnd/destructive_analyzer req_components = list( /obj/item/stock_parts/scanning_module = 1, @@ -835,40 +968,52 @@ /obj/item/circuitboard/machine/experimentor name = "E.X.P.E.R.I-MENTOR (Machine Board)" + icon_state = "science" build_path = /obj/machinery/rnd/experimentor req_components = list( /obj/item/stock_parts/scanning_module = 1, /obj/item/stock_parts/manipulator = 2, /obj/item/stock_parts/micro_laser = 2) +/obj/item/circuitboard/machine/mech_recharger + name = "Mechbay Recharger (Machine Board)" + icon_state = "science" + build_path = /obj/machinery/mech_bay_recharge_port + req_components = list( + /obj/item/stack/cable_coil = 2, + /obj/item/stock_parts/capacitor = 5) + +/obj/item/circuitboard/machine/mechfab + name = "Exosuit Fabricator (Machine Board)" + icon_state = "science" + build_path = /obj/machinery/mecha_part_fabricator + req_components = list( + /obj/item/stock_parts/matter_bin = 2, + /obj/item/stock_parts/manipulator = 1, + /obj/item/stock_parts/micro_laser = 1, + /obj/item/stack/sheet/glass = 1) + +/obj/item/circuitboard/machine/monkey_recycler + name = "Monkey Recycler (Machine Board)" + icon_state = "science" + build_path = /obj/machinery/monkey_recycler + req_components = list( + /obj/item/stock_parts/matter_bin = 1, + /obj/item/stock_parts/manipulator = 1) + needs_anchored = FALSE + /obj/item/circuitboard/machine/nanite_chamber name = "Nanite Chamber (Machine Board)" + icon_state = "science" build_path = /obj/machinery/nanite_chamber req_components = list( /obj/item/stock_parts/scanning_module = 2, /obj/item/stock_parts/micro_laser = 2, /obj/item/stock_parts/manipulator = 1) -/obj/item/circuitboard/machine/public_nanite_chamber - name = "Public Nanite Chamber (Machine Board)" - build_path = /obj/machinery/public_nanite_chamber - var/cloud_id = 1 - req_components = list( - /obj/item/stock_parts/micro_laser = 2, - /obj/item/stock_parts/manipulator = 1) - -/obj/item/circuitboard/machine/public_nanite_chamber/multitool_act(mob/living/user) - var/new_cloud = input("Set the public nanite chamber's Cloud ID (1-100).", "Cloud ID", cloud_id) as num|null - if(new_cloud == null) - return - cloud_id = clamp(round(new_cloud, 1), 1, 100) - -/obj/item/circuitboard/machine/public_nanite_chamber/examine(mob/user) - . = ..() - . += "Cloud ID is currently set to [cloud_id]." - /obj/item/circuitboard/machine/nanite_program_hub name = "Nanite Program Hub (Machine Board)" + icon_state = "science" build_path = /obj/machinery/nanite_program_hub req_components = list( /obj/item/stock_parts/matter_bin = 1, @@ -876,163 +1021,185 @@ /obj/item/circuitboard/machine/nanite_programmer name = "Nanite Programmer (Machine Board)" + icon_state = "science" build_path = /obj/machinery/nanite_programmer req_components = list( /obj/item/stock_parts/manipulator = 2, /obj/item/stock_parts/micro_laser = 2, /obj/item/stock_parts/scanning_module = 1) -/obj/item/circuitboard/machine/protolathe - name = "Protolathe (Machine Board)" - build_path = /obj/machinery/rnd/production/protolathe - req_components = list( - /obj/item/stock_parts/matter_bin = 2, - /obj/item/stock_parts/manipulator = 2, - /obj/item/reagent_containers/glass/beaker = 2) - var/offstation_security_levels = TRUE - -/obj/item/circuitboard/machine/protolathe/offstation - offstation_security_levels = FALSE - -/obj/item/circuitboard/machine/protolathe/rnd_crafted(obj/machinery/rnd/production/P) - offstation_security_levels = P.offstation_security_levels - -/obj/item/circuitboard/machine/protolathe/department - name = "Departmental Protolathe (Machine Board)" - build_path = /obj/machinery/rnd/production/protolathe/department - -/obj/item/circuitboard/machine/protolathe/department/cargo - name = "Departmental Protolathe (Machine Board) - Cargo" - build_path = /obj/machinery/rnd/production/protolathe/department/cargo - -/obj/item/circuitboard/machine/protolathe/department/engineering - name = "Departmental Protolathe (Machine Board) - Engineering" - build_path = /obj/machinery/rnd/production/protolathe/department/engineering - -/obj/item/circuitboard/machine/protolathe/department/medical - name = "Departmental Protolathe (Machine Board) - Medical" - build_path = /obj/machinery/rnd/production/protolathe/department/medical +/obj/item/circuitboard/machine/processor/slime + name = "Slime Processor (Machine Board)" + icon_state = "science" + build_path = /obj/machinery/processor/slime /obj/item/circuitboard/machine/protolathe/department/science name = "Departmental Protolathe (Machine Board) - Science" + icon_state = "science" build_path = /obj/machinery/rnd/production/protolathe/department/science -/obj/item/circuitboard/machine/protolathe/department/security - name = "Departmental Protolathe (Machine Board) - Security" - build_path = /obj/machinery/rnd/production/protolathe/department/security - -/obj/item/circuitboard/machine/protolathe/department/service - name = "Departmental Protolathe - Service (Machine Board)" - build_path = /obj/machinery/rnd/production/protolathe/department/service - -/obj/item/circuitboard/machine/bepis - name = "BEPIS Chamber (Machine Board)" - build_path = /obj/machinery/rnd/bepis +/obj/item/circuitboard/machine/public_nanite_chamber + name = "Public Nanite Chamber (Machine Board)" + icon_state = "science" + build_path = /obj/machinery/public_nanite_chamber + var/cloud_id = 1 req_components = list( - /obj/item/stack/cable_coil = 5, + /obj/item/stock_parts/micro_laser = 2, + /obj/item/stock_parts/manipulator = 1) + +/obj/item/circuitboard/machine/public_nanite_chamber/multitool_act(mob/living/user) + . = ..() + var/new_cloud = input("Set the public nanite chamber's Cloud ID (1-100).", "Cloud ID", cloud_id) as num|null + if(!new_cloud || (loc != user)) + to_chat(user, "You must hold the circuitboard to change its Cloud ID!") + return + cloud_id = clamp(round(new_cloud, 1), 1, 100) + +/obj/item/circuitboard/machine/public_nanite_chamber/examine(mob/user) + . = ..() + . += "Cloud ID is currently set to [cloud_id]." + +/obj/item/circuitboard/machine/quantumpad + name = "Quantum Pad (Machine Board)" + icon_state = "science" + build_path = /obj/machinery/quantumpad + req_components = list( + /obj/item/stack/ore/bluespace_crystal = 1, /obj/item/stock_parts/capacitor = 1, /obj/item/stock_parts/manipulator = 1, - /obj/item/stock_parts/micro_laser = 1, - /obj/item/stock_parts/scanning_module = 1) - -/obj/item/circuitboard/machine/techfab - name = "\improper Techfab (Machine Board)" - build_path = /obj/machinery/rnd/production/techfab - req_components = list( - /obj/item/stock_parts/matter_bin = 2, - /obj/item/stock_parts/manipulator = 2, - /obj/item/reagent_containers/glass/beaker = 2) - -/obj/item/circuitboard/machine/techfab/department - name = "\improper Departmental Techfab (Machine Board)" - build_path = /obj/machinery/rnd/production/techfab/department - -/obj/item/circuitboard/machine/techfab/department/cargo - name = "\improper Departmental Techfab (Machine Board) - Cargo" - build_path = /obj/machinery/rnd/production/techfab/department/cargo - -/obj/item/circuitboard/machine/techfab/department/engineering - name = "\improper Departmental Techfab (Machine Board) - Engineering" - build_path = /obj/machinery/rnd/production/techfab/department/engineering - -/obj/item/circuitboard/machine/techfab/department/medical - name = "\improper Departmental Techfab (Machine Board) - Medical" - build_path = /obj/machinery/rnd/production/techfab/department/medical - -/obj/item/circuitboard/machine/techfab/department/science - name = "\improper Departmental Techfab (Machine Board) - Science" - build_path = /obj/machinery/rnd/production/techfab/department/science - -/obj/item/circuitboard/machine/techfab/department/security - name = "\improper Departmental Techfab (Machine Board) - Security" - build_path = /obj/machinery/rnd/production/techfab/department/security - -/obj/item/circuitboard/machine/techfab/department/service - name = "\improper Departmental Techfab - Service (Machine Board)" - build_path = /obj/machinery/rnd/production/techfab/department/service + /obj/item/stack/cable_coil = 1) + def_components = list(/obj/item/stack/ore/bluespace_crystal = /obj/item/stack/ore/bluespace_crystal/artificial) /obj/item/circuitboard/machine/rdserver name = "R&D Server (Machine Board)" + icon_state = "science" build_path = /obj/machinery/rnd/server req_components = list( /obj/item/stack/cable_coil = 2, /obj/item/stock_parts/scanning_module = 1) -/obj/item/circuitboard/machine/bsa/back - name = "Bluespace Artillery Generator (Machine Board)" - build_path = /obj/machinery/bsa/back //No freebies! - req_components = list( - /obj/item/stock_parts/capacitor/quadratic = 5, - /obj/item/stack/cable_coil = 2) +/obj/item/circuitboard/machine/techfab/department/science + name = "\improper Departmental Techfab (Machine Board) - Science" + icon_state = "science" + build_path = /obj/machinery/rnd/production/techfab/department/science -/obj/item/circuitboard/machine/bsa/middle - name = "Bluespace Artillery Fusor (Machine Board)" - build_path = /obj/machinery/bsa/middle +/obj/item/circuitboard/machine/teleporter_hub + name = "Teleporter Hub (Machine Board)" + icon_state = "science" + build_path = /obj/machinery/teleport/hub req_components = list( - /obj/item/stack/ore/bluespace_crystal = 20, - /obj/item/stack/cable_coil = 2) + /obj/item/stack/ore/bluespace_crystal = 3, + /obj/item/stock_parts/matter_bin = 1) + def_components = list(/obj/item/stack/ore/bluespace_crystal = /obj/item/stack/ore/bluespace_crystal/artificial) -/obj/item/circuitboard/machine/bsa/front - name = "Bluespace Artillery Bore (Machine Board)" - build_path = /obj/machinery/bsa/front +/obj/item/circuitboard/machine/teleporter_station + name = "Teleporter Station (Machine Board)" + icon_state = "science" + build_path = /obj/machinery/teleport/station req_components = list( - /obj/item/stock_parts/manipulator/femto = 5, - /obj/item/stack/cable_coil = 2) + /obj/item/stack/ore/bluespace_crystal = 2, + /obj/item/stock_parts/capacitor = 2, + /obj/item/stack/sheet/glass = 1) + def_components = list(/obj/item/stack/ore/bluespace_crystal = /obj/item/stack/ore/bluespace_crystal/artificial) -/obj/item/circuitboard/machine/dna_vault - name = "DNA Vault (Machine Board)" - build_path = /obj/machinery/dna_vault //No freebies! +/obj/item/circuitboard/machine/dnascanner + name = "DNA Scanner (Machine Board)" + icon_state = "science" + build_path = /obj/machinery/dna_scannernew req_components = list( - /obj/item/stock_parts/capacitor/super = 5, - /obj/item/stock_parts/manipulator/pico = 5, - /obj/item/stack/cable_coil = 2) - -/obj/item/circuitboard/machine/microwave - name = "Microwave (Machine Board)" - build_path = /obj/machinery/microwave - req_components = list( - /obj/item/stock_parts/micro_laser = 1, + /obj/item/stock_parts/scanning_module = 1, /obj/item/stock_parts/matter_bin = 1, - /obj/item/stack/cable_coil = 2, - /obj/item/stack/sheet/glass = 2) + /obj/item/stock_parts/micro_laser = 1, + /obj/item/stack/sheet/glass = 1, + /obj/item/stack/cable_coil = 2) + +// /obj/item/circuitboard/machine/mechpad +// name = "Mecha Orbital Pad (Machine Board)" +// icon_state = "science" +// build_path = /obj/machinery/mechpad +// req_components = list() + +//Security + +/obj/item/circuitboard/machine/protolathe/department/security + name = "Departmental Protolathe (Machine Board) - Security" + icon_state = "security" + build_path = /obj/machinery/rnd/production/protolathe/department/security + +/obj/item/circuitboard/machine/recharger + name = "Weapon Recharger (Machine Board)" + icon_state = "security" + build_path = /obj/machinery/recharger + req_components = list(/obj/item/stock_parts/capacitor = 1) needs_anchored = FALSE -/obj/item/circuitboard/machine/vending/donksofttoyvendor - name = "Donksoft Toy Vendor (Machine Board)" - build_path = /obj/machinery/vending/donksofttoyvendor - req_components = list( - /obj/item/stack/sheet/glass = 1, - /obj/item/vending_refill/donksoft = 1) +/obj/item/circuitboard/machine/techfab/department/security + name = "\improper Departmental Techfab (Machine Board) - Security" + icon_state = "security" + build_path = /obj/machinery/rnd/production/techfab/department/security -/obj/item/circuitboard/machine/vending/syndicatedonksofttoyvendor - name = "Syndicate Donksoft Toy Vendor (Machine Board)" - build_path = /obj/machinery/vending/toyliberationstation +//Service + +/obj/item/circuitboard/machine/biogenerator + name = "Biogenerator (Machine Board)" + icon_state = "service" + build_path = /obj/machinery/biogenerator req_components = list( + /obj/item/stock_parts/matter_bin = 1, + /obj/item/stock_parts/manipulator = 1, + /obj/item/stack/cable_coil = 1, + /obj/item/stack/sheet/glass = 1) + +/obj/item/circuitboard/machine/plantgenes + name = "Plant DNA Manipulator (Machine Board)" + icon_state = "service" + build_path = /obj/machinery/plantgenes + req_components = list( + /obj/item/stock_parts/manipulator = 1, + /obj/item/stock_parts/micro_laser = 1, /obj/item/stack/sheet/glass = 1, - /obj/item/vending_refill/donksoft = 1) + /obj/item/stock_parts/scanning_module = 1) + +/obj/item/circuitboard/machine/plantgenes/vault + name = "alien board (Plant DNA Manipulator)" + icon_state = "abductor_mod" + // It wasn't made by actual abductors race, so no abductor tech here. + def_components = list( + /obj/item/stock_parts/manipulator = /obj/item/stock_parts/manipulator/femto, + /obj/item/stock_parts/micro_laser = /obj/item/stock_parts/micro_laser/quadultra, + /obj/item/stock_parts/scanning_module = /obj/item/stock_parts/scanning_module/triphasic) + +/obj/item/circuitboard/machine/chem_dispenser/drinks + name = "Soda Dispenser (Machine Board)" + icon_state = "service" + build_path = /obj/machinery/chem_dispenser/drinks + +/obj/item/circuitboard/machine/chem_dispenser/drinks/beer + name = "Booze Dispenser (Machine Board)" + icon_state = "service" + build_path = /obj/machinery/chem_dispenser/drinks/beer + +/obj/item/circuitboard/machine/chem_master/condi + name = "CondiMaster 3000 (Machine Board)" + icon_state = "service" + build_path = /obj/machinery/chem_master/condimaster + +/obj/item/circuitboard/machine/deep_fryer + name = "circuit board (Deep Fryer)" + icon_state = "service" + build_path = /obj/machinery/deepfryer + req_components = list(/obj/item/stock_parts/micro_laser = 1) + needs_anchored = FALSE +// /obj/item/circuitboard/machine/griddle +// name = "circuit board (Griddle)" +// icon_state = "service" +// build_path = /obj/machinery/griddle +// req_components = list(/obj/item/stock_parts/micro_laser = 1) +// needs_anchored = FALSE /obj/item/circuitboard/machine/dish_drive name = "Dish Drive (Machine Board)" + icon_state = "service" build_path = /obj/machinery/dish_drive req_components = list( /obj/item/stack/sheet/glass = 1, @@ -1052,73 +1219,195 @@ to_chat(user, "You [suction ? "enable" : "disable"] the board's suction function.") /obj/item/circuitboard/machine/dish_drive/AltClick(mob/living/user) - . = ..() if(!user.Adjacent(src)) return transmit = !transmit to_chat(user, "You [transmit ? "enable" : "disable"] the board's automatic disposal transmission.") - return TRUE -/obj/item/circuitboard/machine/stacking_unit_console - name = "Stacking Machine Console (Machine Board)" - build_path = /obj/machinery/mineral/stacking_unit_console +/obj/item/circuitboard/machine/gibber + name = "Gibber (Machine Board)" + icon_state = "service" + build_path = /obj/machinery/gibber req_components = list( - /obj/item/stack/sheet/glass = 2, - /obj/item/stack/cable_coil = 5) + /obj/item/stock_parts/matter_bin = 1, + /obj/item/stock_parts/manipulator = 1) + needs_anchored = FALSE + +/obj/item/circuitboard/machine/hydroponics + name = "Hydroponics Tray (Machine Board)" + icon_state = "service" + build_path = /obj/machinery/hydroponics/constructable + req_components = list( + /obj/item/stock_parts/matter_bin = 2, + /obj/item/stock_parts/manipulator = 1, + /obj/item/stack/sheet/glass = 1) + needs_anchored = FALSE + +/obj/item/circuitboard/machine/hydroponics/automagic + name = "Automatic Hydroponics Tray (Machine Board)" + build_path = /obj/machinery/hydroponics/constructable/automagic + +/obj/item/circuitboard/machine/microwave + name = "Microwave (Machine Board)" + icon_state = "service" + build_path = /obj/machinery/microwave + req_components = list( + /obj/item/stock_parts/micro_laser = 1, + /obj/item/stock_parts/matter_bin = 1, + /obj/item/stack/cable_coil = 2, + /obj/item/stack/sheet/glass = 2) + needs_anchored = FALSE + +/obj/item/circuitboard/machine/processor + name = "Food Processor (Machine Board)" + icon_state = "service" + build_path = /obj/machinery/processor + req_components = list( + /obj/item/stock_parts/matter_bin = 1, + /obj/item/stock_parts/manipulator = 1) + needs_anchored = FALSE + +/obj/item/circuitboard/machine/processor/attackby(obj/item/I, mob/user, params) + if(I.tool_behaviour == TOOL_SCREWDRIVER) + if(build_path == /obj/machinery/processor) + name = "Slime Processor (Machine Board)" + build_path = /obj/machinery/processor/slime + to_chat(user, "Name protocols successfully updated.") + else + name = "Food Processor (Machine Board)" + build_path = /obj/machinery/processor + to_chat(user, "Defaulting name protocols.") + else + return ..() + +/obj/item/circuitboard/machine/protolathe/department/service + name = "Departmental Protolathe - Service (Machine Board)" + icon_state = "service" + build_path = /obj/machinery/rnd/production/protolathe/department/service + +/obj/item/circuitboard/machine/recycler + name = "Recycler (Machine Board)" + icon_state = "service" + build_path = /obj/machinery/recycler + req_components = list( + /obj/item/stock_parts/matter_bin = 1, + /obj/item/stock_parts/manipulator = 1) + needs_anchored = FALSE + +/obj/item/circuitboard/machine/seed_extractor + name = "Seed Extractor (Machine Board)" + icon_state = "service" + build_path = /obj/machinery/seed_extractor + req_components = list( + /obj/item/stock_parts/matter_bin = 1, + /obj/item/stock_parts/manipulator = 1) + needs_anchored = FALSE + +/obj/item/circuitboard/machine/techfab/department/service + name = "\improper Departmental Techfab - Service (Machine Board)" + icon_state = "service" + build_path = /obj/machinery/rnd/production/techfab/department/service + +/obj/item/circuitboard/machine/vendatray + name = "Vend-A-Tray (Machine Board)" + icon_state = "service" + build_path = /obj/structure/displaycase/forsale + req_components = list() + // /obj/item/stock_parts/card_reader = 1) + +//Supply + +/obj/item/circuitboard/machine/mining_equipment_vendor + name = "Mining Equipment Vendor (Machine Board)" + icon_state = "supply" + build_path = /obj/machinery/mineral/equipment_vendor + req_components = list( + /obj/item/stack/sheet/glass = 1, + /obj/item/stock_parts/matter_bin = 3) + +/obj/item/circuitboard/machine/mining_equipment_vendor/golem + name = "Golem Ship Equipment Vendor (Machine Board)" + build_path = /obj/machinery/mineral/equipment_vendor/golem + +/obj/item/circuitboard/machine/ore_redemption + name = "Ore Redemption (Machine Board)" + icon_state = "supply" + build_path = /obj/machinery/mineral/ore_redemption + req_components = list( + /obj/item/stack/sheet/glass = 1, + /obj/item/stock_parts/matter_bin = 1, + /obj/item/stock_parts/micro_laser = 1, + /obj/item/stock_parts/manipulator = 1, + /obj/item/assembly/igniter = 1) + needs_anchored = FALSE + +/obj/item/circuitboard/machine/ore_silo + name = "Ore Silo (Machine Board)" + icon_state = "supply" + build_path = /obj/machinery/ore_silo + req_components = list() + +/obj/item/circuitboard/machine/protolathe/department/cargo + name = "Departmental Protolathe (Machine Board) - Cargo" + icon_state = "supply" + build_path = /obj/machinery/rnd/production/protolathe/department/cargo /obj/item/circuitboard/machine/stacking_machine name = "Stacking Machine (Machine Board)" + icon_state = "supply" build_path = /obj/machinery/mineral/stacking_machine req_components = list( /obj/item/stock_parts/manipulator = 2, /obj/item/stock_parts/matter_bin = 2) -/obj/item/circuitboard/machine/generator - name = "Thermo-Electric Generator (Machine Board)" - build_path = /obj/machinery/power/generator - req_components = list() - -/obj/item/circuitboard/machine/circulator - name = "Circulator/Heat Exchanger (Machine Board)" - build_path = /obj/machinery/atmospherics/components/binary/circulator - req_components = list() - -/obj/item/circuitboard/machine/harvester - name = "Harvester (Machine Board)" - build_path = /obj/machinery/harvester - req_components = list(/obj/item/stock_parts/micro_laser = 4) - -/obj/item/circuitboard/machine/ore_silo - name = "Ore Silo (Machine Board)" - build_path = /obj/machinery/ore_silo - req_components = list() - -/obj/item/circuitboard/machine/paystand - name = "Pay Stand (Machine Board)" - build_path = /obj/machinery/paystand - req_components = list() - -/obj/item/circuitboard/machine/autobottler - name = "Auto-Bottler (Machine Board)" - build_path = /obj/machinery/rnd/production/protolathe/department/autobottler //Manips make you print things cheaper, even chems - req_components = list(/obj/item/stock_parts/matter_bin = 5, - /obj/item/stack/sheet/glass = 2, - /obj/item/stock_parts/capacitor = 1, - /obj/item/stack/cable_coil = 5, - /obj/item/reagent_containers/glass/beaker = 6) //So it can hold lots of chems - -/obj/item/circuitboard/machine/kinkmate - name = "Kinkmate Vendor (Machine Board)" - build_path = /obj/machinery/vending/kink - req_components = list(/obj/item/vending_refill/kink = 1) - -/obj/item/circuitboard/machine/autolathe/toy - name = "Autoylathe (Machine Board)" - build_path = /obj/machinery/autolathe/toy +/obj/item/circuitboard/machine/stacking_unit_console + name = "Stacking Machine Console (Machine Board)" + icon_state = "supply" + build_path = /obj/machinery/mineral/stacking_unit_console req_components = list( - /obj/item/stock_parts/matter_bin = 3, + /obj/item/stack/sheet/glass = 2, + /obj/item/stack/cable_coil = 5) + +/obj/item/circuitboard/machine/techfab/department/cargo + name = "\improper Departmental Techfab (Machine Board) - Cargo" + icon_state = "supply" + build_path = /obj/machinery/rnd/production/techfab/department/cargo + +/obj/item/circuitboard/machine/bepis + name = "BEPIS Chamber (Machine Board)" + icon_state = "supply" + build_path = /obj/machinery/rnd/bepis + req_components = list( + /obj/item/stack/cable_coil = 5, + /obj/item/stock_parts/capacitor = 1, /obj/item/stock_parts/manipulator = 1, - /obj/item/stack/sheet/glass = 1) + /obj/item/stock_parts/micro_laser = 1, + /obj/item/stock_parts/scanning_module = 1) + +//Misc +/obj/item/circuitboard/machine/sheetifier + name = "Sheet-meister 2000 (Machine Board)" + icon_state = "supply" + build_path = /obj/machinery/sheetifier + req_components = list( + /obj/item/stock_parts/manipulator = 2, + /obj/item/stock_parts/matter_bin = 2) + needs_anchored = FALSE + +/obj/item/circuitboard/machine/abductor + name = "alien board (Report This)" + icon_state = "abductor_mod" + +/obj/item/circuitboard/machine/abductor/core + name = "alien board (Void Core)" + build_path = /obj/machinery/power/rtg/abductor + req_components = list( + /obj/item/stock_parts/capacitor = 1, + /obj/item/stock_parts/micro_laser = 1, + /obj/item/stock_parts/cell/infinite/abductor = 1) + def_components = list( + /obj/item/stock_parts/capacitor = /obj/item/stock_parts/capacitor/quadratic, + /obj/item/stock_parts/micro_laser = /obj/item/stock_parts/micro_laser/quadultra) /obj/item/circuitboard/machine/hypnochair name = "Enhanced Interrogation Chamber (Machine Board)" @@ -1129,13 +1418,35 @@ /obj/item/stock_parts/scanning_module = 2 ) +// /obj/item/circuitboard/machine/plumbing_receiver +// name = "Chemical Recipient (Machine Board)" +// icon_state = "medical" +// build_path = /obj/machinery/plumbing/receiver +// req_components = list( +// /obj/item/stack/ore/bluespace_crystal = 1, +// /obj/item/stock_parts/capacitor = 2, +// /obj/item/stack/sheet/glass = 1) +// def_components = list(/obj/item/stack/ore/bluespace_crystal = /obj/item/stack/ore/bluespace_crystal/artificial) +// needs_anchored = FALSE + +// /obj/item/circuitboard/machine/skill_station +// name = "Skill Station (Machine Board)" +// build_path = /obj/machinery/skill_station +// req_components = list( +// /obj/item/stock_parts/matter_bin = 2, +// /obj/item/stock_parts/micro_laser = 2, +// /obj/item/stock_parts/scanning_module = 2 +// ) +// DIY SHUTTLE /obj/item/circuitboard/machine/shuttle/engine name = "Thruster (Machine Board)" + icon_state = "engineering" build_path = /obj/machinery/shuttle/engine req_components = list() /obj/item/circuitboard/machine/shuttle/engine/plasma name = "Plasma Thruster (Machine Board)" + icon_state = "engineering" build_path = /obj/machinery/shuttle/engine/plasma req_components = list(/obj/item/stock_parts/capacitor = 2, /obj/item/stack/cable_coil = 5, @@ -1143,6 +1454,7 @@ /obj/item/circuitboard/machine/shuttle/engine/void name = "Void Thruster (Machine Board)" + icon_state = "engineering" build_path = /obj/machinery/shuttle/engine/void req_components = list(/obj/item/stock_parts/capacitor/quadratic = 2, /obj/item/stack/cable_coil = 5, @@ -1150,11 +1462,13 @@ /obj/item/circuitboard/machine/shuttle/heater name = "Electronic Engine Heater (Machine Board)" + icon_state = "engineering" build_path = /obj/machinery/atmospherics/components/unary/shuttle/heater req_components = list(/obj/item/stock_parts/micro_laser = 2, /obj/item/stock_parts/matter_bin = 1) /obj/item/circuitboard/machine/explosive_compressor name = "Explosive Compressor (Machine Board)" + icon_state = "engineering" build_path = /obj/machinery/research/explosive_compressor req_components = list(/obj/item/stock_parts/matter_bin = 3) diff --git a/code/game/objects/items/defib.dm b/code/game/objects/items/defib.dm index 25a5bcc800..90912f7409 100644 --- a/code/game/objects/items/defib.dm +++ b/code/game/objects/items/defib.dm @@ -721,24 +721,24 @@ /obj/item/disk/medical/defib_heal name = "Defibrillator Healing Disk" - desc = "An upgrade which increases the healing power of the defibrillator" + desc = "An upgrade which increases the healing power of the defibrillator." icon_state = "heal_disk" custom_materials = list(/datum/material/iron=16000, /datum/material/glass = 18000, /datum/material/gold = 6000, /datum/material/silver = 6000) /obj/item/disk/medical/defib_shock name = "Defibrillator Anti-Shock Disk" - desc = "A safety upgrade that guarantees only the patient will get shocked" + desc = "A safety upgrade that guarantees only the patient will get shocked." icon_state = "zap_disk" custom_materials = list(/datum/material/iron=16000, /datum/material/glass = 18000, /datum/material/gold = 6000, /datum/material/silver = 6000) /obj/item/disk/medical/defib_decay name = "Defibrillator Body-Decay Extender Disk" - desc = "An upgrade allowing the defibrillator to work on more decayed bodies" + desc = "An upgrade allowing the defibrillator to work on bodies that have decayed further." icon_state = "body_disk" custom_materials = list(/datum/material/iron=16000, /datum/material/glass = 18000, /datum/material/gold = 16000, /datum/material/silver = 6000, /datum/material/titanium = 2000) /obj/item/disk/medical/defib_speed name = "Defibrillator Fast Charge Disk" - desc = "An upgrade to the defibrillator capacitors, which let it charge faster" + desc = "An upgrade to the defibrillator capacitors, which lets it charge faster." icon_state = "fast_disk" custom_materials = list(/datum/material/iron=16000, /datum/material/glass = 8000, /datum/material/gold = 26000, /datum/material/silver = 26000) diff --git a/code/game/objects/items/devices/paicard.dm b/code/game/objects/items/devices/paicard.dm index 9a722feb6a..e7a9d51ebe 100644 --- a/code/game/objects/items/devices/paicard.dm +++ b/code/game/objects/items/devices/paicard.dm @@ -122,7 +122,10 @@ /obj/item/paicard/proc/setPersonality(mob/living/silicon/pai/personality) src.pai = personality src.add_overlay("pai-null") - + var/list/policies = CONFIG_GET(keyed_list/policyconfig) + var/policy = policies[POLICYCONFIG_PAI] + if(policy) + to_chat(personality, policy) playsound(loc, 'sound/effects/pai_boot.ogg', 50, 1, -1) audible_message("\The [src] plays a cheerful startup noise!") diff --git a/code/game/objects/items/flamethrower.dm b/code/game/objects/items/flamethrower.dm index 3d1ea9e7a2..73299a2006 100644 --- a/code/game/objects/items/flamethrower.dm +++ b/code/game/objects/items/flamethrower.dm @@ -180,6 +180,11 @@ //Called from turf.dm turf/dblclick /obj/item/flamethrower/proc/flame_turf(turflist) + var/mob/living/carbon/human/user = loc + // no fun for pacifists + if(HAS_TRAIT(user, TRAIT_PACIFISM)) + to_chat(user, "You don't want to put others in danger!") + return if(!lit || operating) return operating = TRUE diff --git a/code/game/objects/items/his_grace.dm b/code/game/objects/items/his_grace.dm index 6e270b6374..eafa03ca61 100644 --- a/code/game/objects/items/his_grace.dm +++ b/code/game/objects/items/his_grace.dm @@ -12,7 +12,7 @@ lefthand_file = 'icons/mob/inhands/equipment/toolbox_lefthand.dmi' righthand_file = 'icons/mob/inhands/equipment/toolbox_righthand.dmi' icon = 'icons/obj/items_and_weapons.dmi' - w_class = WEIGHT_CLASS_BULKY + w_class = WEIGHT_CLASS_GIGANTIC force = 12 total_mass = TOTAL_MASS_NORMAL_ITEM // average toolbox attack_verb = list("robusted") @@ -70,19 +70,19 @@ else . += "[src] is latched closed." -/obj/item/his_grace/relaymove(mob/living/user) //Allows changelings, etc. to climb out of Him after they revive, provided He isn't active +/obj/item/his_grace/relaymove(mob/living/user, direction) //Allows changelings, etc. to climb out of Him after they revive, provided He isn't active if(!awakened) user.forceMove(get_turf(src)) user.visible_message("[user] scrambles out of [src]!", "You climb out of [src]!") -/obj/item/his_grace/process() +/obj/item/his_grace/process(delta_time) if(!bloodthirst) drowse() return if(bloodthirst < HIS_GRACE_CONSUME_OWNER && !ascended) - adjust_bloodthirst(1 + FLOOR(LAZYLEN(contents) * 0.5, 1)) //Maybe adjust this? + adjust_bloodthirst((1 + FLOOR(LAZYLEN(contents) * 0.5, 1)) * delta_time) //Maybe adjust this? else - adjust_bloodthirst(1) //don't cool off rapidly once we're at the point where His Grace consumes all. + adjust_bloodthirst(1 * delta_time) //don't cool off rapidly once we're at the point where His Grace consumes all. var/mob/living/master = get_atom_on_turf(src, /mob/living) if(istype(master) && (src in master.held_items)) switch(bloodthirst) @@ -94,7 +94,7 @@ REMOVE_TRAIT(src, TRAIT_NODROP, HIS_GRACE_TRAIT) master.DefaultCombatKnockdown(60) master.adjustBruteLoss(master.maxHealth) - playsound(master, 'sound/effects/splat.ogg', 100, 0) + playsound(master, 'sound/effects/splat.ogg', 100, FALSE) else master.apply_status_effect(STATUS_EFFECT_HISGRACE) return @@ -115,8 +115,8 @@ if(!L.stat) L.visible_message("[src] lunges at [L]!", "[src] lunges at you!") do_attack_animation(L, null, src) - playsound(L, 'sound/weapons/smash.ogg', 50, 1) - playsound(L, 'sound/misc/desceration-01.ogg', 50, 1) + playsound(L, 'sound/weapons/smash.ogg', 50, TRUE) + playsound(L, 'sound/misc/desceration-01.ogg', 50, TRUE) L.adjustBruteLoss(force) adjust_bloodthirst(-5) //Don't stop attacking they're right there! else @@ -137,6 +137,8 @@ move_gracefully() /obj/item/his_grace/proc/move_gracefully() + SIGNAL_HANDLER + if(!awakened) return var/static/list/transforms @@ -161,7 +163,7 @@ return var/turf/T = get_turf(src) T.visible_message("[src] slowly stops rattling and falls still, His latch snapping shut.") - playsound(loc, 'sound/weapons/batonextend.ogg', 100, 1) + playsound(loc, 'sound/weapons/batonextend.ogg', 100, TRUE) name = initial(name) desc = initial(desc) icon_state = initial(icon_state) @@ -178,8 +180,8 @@ var/victims = 0 meal.visible_message("[src] swings open and devours [meal]!", "[src] consumes you!") meal.adjustBruteLoss(200) - playsound(meal, 'sound/misc/desceration-02.ogg', 75, 1) - playsound(src, 'sound/items/eatfood.ogg', 100, 1) + playsound(meal, 'sound/misc/desceration-02.ogg', 75, TRUE) + playsound(src, 'sound/items/eatfood.ogg', 100, TRUE) meal.forceMove(src) force_bonus += HIS_GRACE_FORCE_BONUS prev_bloodthirst = bloodthirst @@ -253,3 +255,4 @@ if(istype(master)) master.visible_message("Gods will be watching.") name = "[master]'s mythical toolbox of three powers" + master.client?.give_award(/datum/award/achievement/misc/ascension, master) diff --git a/code/game/objects/items/melee/misc.dm b/code/game/objects/items/melee/misc.dm index a6e85fadb8..1043876061 100644 --- a/code/game/objects/items/melee/misc.dm +++ b/code/game/objects/items/melee/misc.dm @@ -3,9 +3,8 @@ /obj/item/melee/proc/check_martial_counter(mob/living/carbon/human/target, mob/living/carbon/human/user) if(target.check_martial_melee_block()) - target.visible_message("[target.name] blocks [src] and twists [user]'s arm behind [user.p_their()] back!", + target.visible_message("[target.name] blocks your attack!", "You block the attack!") - user.Stun(40) return TRUE /obj/item/melee/chainofcommand diff --git a/code/game/objects/items/pet_carrier.dm b/code/game/objects/items/pet_carrier.dm index ef2fb44d3d..f0d66d9097 100644 --- a/code/game/objects/items/pet_carrier.dm +++ b/code/game/objects/items/pet_carrier.dm @@ -328,6 +328,9 @@ REMOVE_TRAIT(occupant, TRAIT_RESISTHIGHPRESSURE, "bluespace_container_resist_high_pressure") REMOVE_TRAIT(occupant, TRAIT_RESISTLOWPRESSURE, "bluespace_container_resist_low_pressure") name = initial(name) + if(iscarbon(occupant)) + to_chat(occupant, "You pop out of the [src], slightly dazed!") + occupant.Stun(5 SECONDS) /obj/item/pet_carrier/bluespace/return_air() if(!occupant_gas_supply) diff --git a/code/game/objects/items/stacks/cash.dm b/code/game/objects/items/stacks/cash.dm index e2036835ce..68762a63c7 100644 --- a/code/game/objects/items/stacks/cash.dm +++ b/code/game/objects/items/stacks/cash.dm @@ -14,7 +14,7 @@ grind_results = list(/datum/reagent/cellulose = 10) var/value = 0 -/obj/item/stack/spacecash/Initialize() +/obj/item/stack/spacecash/Initialize(mapload, new_amount, merge = TRUE) . = ..() update_desc() diff --git a/code/game/objects/items/stacks/sheets/glass.dm b/code/game/objects/items/stacks/sheets/glass.dm index de2e3b89fe..026b73ca15 100644 --- a/code/game/objects/items/stacks/sheets/glass.dm +++ b/code/game/objects/items/stacks/sheets/glass.dm @@ -41,6 +41,8 @@ GLOBAL_LIST_INIT(glass_recipes, list ( \ material_type = /datum/material/glass point_value = 1 tableVariant = /obj/structure/table/glass + matter_amount = 4 + cost = 500 shard_type = /obj/item/shard /obj/item/stack/sheet/glass/suicide_act(mob/living/carbon/user) @@ -69,12 +71,13 @@ GLOBAL_LIST_INIT(glass_recipes, list ( \ if (get_amount() < 1 || CC.get_amount() < 5) to_chat(user, "You attach wire to the [name].") var/obj/item/stack/light_w/new_tile = new(user.loc) new_tile.add_fingerprint(user) - else if(istype(W, /obj/item/stack/rods)) + return + if(istype(W, /obj/item/stack/rods)) var/obj/item/stack/rods/V = W if (V.get_amount() >= 1 && get_amount() >= 1) var/obj/item/stack/sheet/rglass/RG = new (get_turf(user)) @@ -86,9 +89,8 @@ GLOBAL_LIST_INIT(glass_recipes, list ( \ user.put_in_hands(RG) else to_chat(user, "You need one rod and one sheet of glass to make reinforced glass!") - return - else - return ..() + return + return ..() @@ -164,6 +166,7 @@ GLOBAL_LIST_INIT(reinforced_glass_recipes, list ( \ merge_type = /obj/item/stack/sheet/rglass grind_results = list(/datum/reagent/silicon = 20, /datum/reagent/iron = 10) point_value = 4 + matter_amount = 6 shard_type = /obj/item/shard /obj/item/stack/sheet/rglass/attackby(obj/item/W, mob/user, params) @@ -211,6 +214,7 @@ GLOBAL_LIST_INIT(prglass_recipes, list ( \ merge_type = /obj/item/stack/sheet/plasmarglass grind_results = list(/datum/reagent/silicon = 20, /datum/reagent/toxin/plasma = 10, /datum/reagent/iron = 10) point_value = 23 + matter_amount = 8 shard_type = /obj/item/shard/plasma /obj/item/stack/sheet/plasmarglass/get_main_recipes() diff --git a/code/game/objects/items/stacks/sheets/sheets.dm b/code/game/objects/items/stacks/sheets/sheets.dm index 57c8ba75d8..57d0659ef6 100644 --- a/code/game/objects/items/stacks/sheets/sheets.dm +++ b/code/game/objects/items/stacks/sheets/sheets.dm @@ -19,7 +19,7 @@ ///What type of wall does this sheet spawn var/walltype -/obj/item/stack/sheet/Initialize(mapload, new_amount, merge) +/obj/item/stack/sheet/Initialize(mapload, new_amount, merge = TRUE) . = ..() pixel_x = rand(-4, 4) pixel_y = rand(-4, 4) diff --git a/code/game/objects/items/stacks/stack.dm b/code/game/objects/items/stacks/stack.dm index 520a400630..7b251c075b 100644 --- a/code/game/objects/items/stacks/stack.dm +++ b/code/game/objects/items/stacks/stack.dm @@ -8,15 +8,18 @@ /* * Stacks */ + /obj/item/stack icon = 'icons/obj/stack_objects.dmi' gender = PLURAL material_modifier = 0.01 + // material_modifier = 0.05 //5%, so that a 50 sheet stack has the effect of 5k materials instead of 100k. + max_integrity = 100 var/list/datum/stack_recipe/recipes var/singular_name var/amount = 1 var/max_amount = 50 //also see stack recipes initialisation, param "max_res_amount" must be equal to this max_amount - var/is_cyborg = 0 // It's 1 if module is used by a cyborg, and uses its storage + var/is_cyborg = FALSE // It's TRUE if module is used by a cyborg, and uses its storage var/datum/robot_energy_storage/source var/cost = 1 // How much energy from storage it costs var/merge_type = null // This path and its children should merge with this stack, defaults to src.type @@ -25,7 +28,6 @@ var/list/mats_per_unit //list that tells you how much is in a single unit. ///Datum material type that this stack is made of var/material_type - max_integrity = 100 //NOTE: When adding grind_results, the amounts should be for an INDIVIDUAL ITEM - these amounts will be multiplied by the stack size in on_grind() var/obj/structure/table/tableVariant // we tables now (stores table variant to be built from this stack) @@ -36,16 +38,8 @@ var/absorption_capacity /// How quickly we lower the blood flow on a cut wound we're bandaging. Expected lifetime of this bandage in ticks is thus absorption_capacity/absorption_rate, or until the cut heals, whichever comes first var/absorption_rate - -/obj/item/stack/on_grind() - for(var/i in 1 to grind_results.len) //This should only call if it's ground, so no need to check if grind_results exists - grind_results[grind_results[i]] *= get_amount() //Gets the key at position i, then the reagent amount of that key, then multiplies it by stack size - -/obj/item/stack/grind_requirements() - if(is_cyborg) - to_chat(usr, "[src] is electronically synthesized in your chassis and can't be ground up!") - return - return TRUE + /// Amount of matter for RCD + var/matter_amount = 0 /obj/item/stack/Initialize(mapload, new_amount, merge = TRUE) if(new_amount != null) @@ -55,16 +49,19 @@ new type(loc, max_amount, FALSE) if(!merge_type) merge_type = type - if(custom_materials && custom_materials.len) - mats_per_unit = list() - var/in_process_mat_list = custom_materials.Copy() + + if(LAZYLEN(mats_per_unit)) + set_mats_per_unit(mats_per_unit, 1) + else if(LAZYLEN(custom_materials)) + // DO NOT REMOVE! we have to inflate the values first for(var/i in custom_materials) - mats_per_unit[SSmaterials.GetMaterialRef(i)] = in_process_mat_list[i] custom_materials[i] *= amount + set_mats_per_unit(custom_materials, amount ? 1/amount : 1) + . = ..() if(merge) for(var/obj/item/stack/S in loc) - if(S.merge_type == merge_type) + if(can_merge(S)) INVOKE_ASYNC(src, .proc/merge, S) var/list/temp_recipes = get_main_recipes() recipes = temp_recipes.Copy() @@ -73,12 +70,48 @@ for(var/i in M.categories) switch(i) if(MAT_CATEGORY_BASE_RECIPES) + var/list/temp = SSmaterials.base_stack_recipes.Copy() + recipes += temp + if(MAT_CATEGORY_RIGID) var/list/temp = SSmaterials.rigid_stack_recipes.Copy() recipes += temp update_weight() update_icon() +/** Sets the amount of materials per unit for this stack. + * + * Arguments: + * - [mats][/list]: The value to set the mats per unit to. + * - multiplier: The amount to multiply the mats per unit by. Defaults to 1. + */ +/obj/item/stack/proc/set_mats_per_unit(list/mats, multiplier=1) + mats_per_unit = SSmaterials.FindOrCreateMaterialCombo(mats, multiplier) + update_custom_materials() + +/** Updates the custom materials list of this stack. + */ +/obj/item/stack/proc/update_custom_materials() + set_custom_materials(mats_per_unit, amount, is_update=TRUE) + +/** + * Override to make things like metalgen accurately set custom materials + */ +/obj/item/stack/set_custom_materials(list/materials, multiplier=1, is_update=FALSE) + return is_update ? ..() : set_mats_per_unit(materials, multiplier / (amount || 1)) + +/obj/item/stack/on_grind() + . = ..() + for(var/i in 1 to length(grind_results)) //This should only call if it's ground, so no need to check if grind_results exists + grind_results[grind_results[i]] *= get_amount() //Gets the key at position i, then the reagent amount of that key, then multiplies it by stack size + +/obj/item/stack/grind_requirements() + if(is_cyborg) + to_chat(usr, "[src] is electronically synthesized in your chassis and can't be ground up!") + return + return TRUE + /obj/item/stack/proc/get_main_recipes() + SHOULD_CALL_PARENT(TRUE) return list()//empty list /obj/item/stack/proc/update_weight() @@ -99,12 +132,6 @@ else icon_state = "[initial(icon_state)]_3" - -/obj/item/stack/Destroy() - if (usr && usr.machine==src) - usr << browse(null, "window=stack") - . = ..() - /obj/item/stack/examine(mob/user) . = ..() if (is_cyborg) @@ -126,196 +153,208 @@ /obj/item/stack/proc/get_amount() if(is_cyborg) - . = round(source.energy / cost) + . = round(source?.energy / cost) else . = (amount) -/obj/item/stack/attack_self(mob/user) - interact(user) +/** + * Builds all recipes in a given recipe list and returns an association list containing them + * + * Arguments: + * * recipe_to_iterate - The list of recipes we are using to build recipes + */ +/obj/item/stack/proc/recursively_build_recipes(list/recipe_to_iterate) + var/list/L = list() + for(var/recipe in recipe_to_iterate) + if(istype(recipe, /datum/stack_recipe_list)) + var/datum/stack_recipe_list/R = recipe + L["[R.title]"] = recursively_build_recipes(R.recipes) + if(istype(recipe, /datum/stack_recipe)) + var/datum/stack_recipe/R = recipe + L["[R.title]"] = build_recipe(R) + return L -/obj/item/stack/interact(mob/user, sublist) - ui_interact(user, sublist) +/** + * Returns a list of properties of a given recipe + * + * Arguments: + * * R - The stack recipe we are using to get a list of properties + */ +/obj/item/stack/proc/build_recipe(datum/stack_recipe/R) + return list( + "res_amount" = R.res_amount, + "max_res_amount" = R.max_res_amount, + "req_amount" = R.req_amount, + "ref" = "\ref[R]", + ) -/obj/item/stack/ui_interact(mob/user, recipes_sublist) +/** + * Checks if the recipe is valid to be used + * + * Arguments: + * * R - The stack recipe we are checking if it is valid + * * recipe_list - The list of recipes we are using to check the given recipe + */ +/obj/item/stack/proc/is_valid_recipe(datum/stack_recipe/R, list/recipe_list) + for(var/S in recipe_list) + if(S == R) + return TRUE + if(istype(S, /datum/stack_recipe_list)) + var/datum/stack_recipe_list/L = S + if(is_valid_recipe(R, L.recipes)) + return TRUE + return FALSE + +/obj/item/stack/ui_state(mob/user) + return GLOB.hands_state + +/obj/item/stack/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "Stack", name) + ui.open() + +/obj/item/stack/ui_data(mob/user) + var/list/data = list() + data["amount"] = get_amount() + return data + +/obj/item/stack/ui_static_data(mob/user) + var/list/data = list() + data["recipes"] = recursively_build_recipes(recipes) + return data + +/obj/item/stack/ui_act(action, params) . = ..() - if (!recipes) + if(.) return - if (!src || get_amount() <= 0) - user << browse(null, "window=stack") - user.set_machine(src) //for correct work of onclose - var/list/recipe_list = recipes - if (recipes_sublist && recipe_list[recipes_sublist] && istype(recipe_list[recipes_sublist], /datum/stack_recipe_list)) - var/datum/stack_recipe_list/srl = recipe_list[recipes_sublist] - recipe_list = srl.recipes - var/t1 = "Amount Left: [get_amount()]
" - for(var/i in 1 to length(recipe_list)) - var/E = recipe_list[i] - if (isnull(E)) - t1 += "
" - continue - if (i>1 && !isnull(recipe_list[i-1])) - t1+="
" - if (istype(E, /datum/stack_recipe_list)) - var/datum/stack_recipe_list/srl = E - t1 += "[srl.title]" - - if (istype(E, /datum/stack_recipe)) - var/datum/stack_recipe/R = E - var/max_multiplier = round(get_amount() / R.req_amount) - var/title - var/can_build = 1 - can_build = can_build && (max_multiplier>0) - - if (R.res_amount>1) - title+= "[R.res_amount]x [R.title]\s" - else - title+= "[R.title]" - title+= " ([R.req_amount] [singular_name]\s)" - if (can_build) - t1 += text("[title] ") - else - t1 += text("[]", title) - continue - if (R.max_res_amount>1 && max_multiplier>1) - max_multiplier = min(max_multiplier, round(R.max_res_amount/R.res_amount)) - t1 += " |" - var/list/multipliers = list(5,10,25) - for (var/n in multipliers) - if (max_multiplier>=n) - t1 += " [n*R.res_amount]x" - if (!(max_multiplier in multipliers)) - t1 += " [max_multiplier*R.res_amount]x" - - var/datum/browser/popup = new(user, "stack", name, 400, 400) - popup.set_content(t1) - popup.open(0) - onclose(user, "stack") - -/obj/item/stack/Topic(href, href_list) - ..() - if (usr.restrained() || usr.stat || usr.get_active_held_item() != src) - return - if (href_list["sublist"] && !href_list["make"]) - interact(usr, text2num(href_list["sublist"])) - if (href_list["make"]) - if (get_amount() < 1 && !is_cyborg) - qdel(src) - - var/list/recipes_list = recipes - if (href_list["sublist"]) - var/datum/stack_recipe_list/srl = recipes_list[text2num(href_list["sublist"])] - recipes_list = srl.recipes - var/datum/stack_recipe/R = recipes_list[text2num(href_list["make"])] - var/multiplier = text2num(href_list["multiplier"]) - if (!multiplier ||(multiplier <= 0)) //href protection - return - if(!building_checks(R, multiplier)) - return - if (R.time) - var/adjusted_time = 0 - usr.visible_message("[usr] starts building [R.title].", "You start building [R.title]...") - if(HAS_TRAIT(usr, R.trait_booster)) - adjusted_time = (R.time * R.trait_modifier) - else - adjusted_time = R.time - if (!do_after(usr, adjusted_time, target = usr)) + switch(action) + if("make") + if(get_amount() < 1 && !is_cyborg) + qdel(src) + return + var/datum/stack_recipe/R = locate(params["ref"]) + if(!is_valid_recipe(R, recipes)) //href exploit protection + return + var/multiplier = text2num(params["multiplier"]) + if(!multiplier || (multiplier <= 0)) //href exploit protection return if(!building_checks(R, multiplier)) return + if(R.time) + var/adjusted_time = 0 + usr.visible_message("[usr] starts building \a [R.title].", "You start building \a [R.title]...") + if(HAS_TRAIT(usr, R.trait_booster)) + adjusted_time = (R.time * R.trait_modifier) + else + adjusted_time = R.time + if(!do_after(usr, adjusted_time, target = usr)) + return + if(!building_checks(R, multiplier)) + return - var/obj/O - if(R.max_res_amount > 1) //Is it a stack? - O = new R.result_type(usr.drop_location(), R.res_amount * multiplier) - else if(ispath(R.result_type, /turf)) - var/turf/T = usr.drop_location() - if(!isturf(T)) - return - T.PlaceOnTop(R.result_type, flags = CHANGETURF_INHERIT_AIR) + var/obj/O + if(R.max_res_amount > 1) //Is it a stack? + O = new R.result_type(usr.drop_location(), R.res_amount * multiplier) + else if(ispath(R.result_type, /turf)) + var/turf/T = usr.drop_location() + if(!isturf(T)) + return + T.PlaceOnTop(R.result_type, flags = CHANGETURF_INHERIT_AIR) + else + O = new R.result_type(usr.drop_location()) + if(O) + O.setDir(usr.dir) + use(R.req_amount * multiplier) + + if(R.applies_mats && LAZYLEN(mats_per_unit)) + if(isstack(O)) + var/obj/item/stack/crafted_stack = O + crafted_stack.set_mats_per_unit(mats_per_unit, R.req_amount / R.res_amount) + else + O.set_custom_materials(mats_per_unit, R.req_amount / R.res_amount) + + if(istype(O, /obj/structure/windoor_assembly)) + var/obj/structure/windoor_assembly/W = O + W.ini_dir = W.dir + else if(istype(O, /obj/structure/window)) + var/obj/structure/window/W = O + W.ini_dir = W.dir + + if(QDELETED(O)) + return //It's a stack and has already been merged + + if(isitem(O)) + usr.put_in_hands(O) + O.add_fingerprint(usr) + + //BubbleWrap - so newly formed boxes are empty + if(istype(O, /obj/item/storage)) + for (var/obj/item/I in O) + qdel(I) + //BubbleWrap END + return TRUE + +/obj/item/stack/vv_edit_var(vname, vval) + if(vname == NAMEOF(src, amount)) + add(clamp(vval, 1-amount, max_amount - amount)) //there must always be one. + return TRUE + else if(vname == NAMEOF(src, max_amount)) + max_amount = max(vval, 1) + add((max_amount < amount) ? (max_amount - amount) : 0) //update icon, weight, ect + return TRUE + return ..() + +/obj/item/stack/proc/building_checks(datum/stack_recipe/recipe, multiplier) + if (get_amount() < recipe.req_amount*multiplier) + if (recipe.req_amount*multiplier>1) + to_chat(usr, "You haven't got enough [src] to build \the [recipe.req_amount*multiplier] [recipe.title]\s!") else - O = new R.result_type(get_turf(usr)) - if(O) - O.setDir(usr.dir) - log_craft("[O] crafted by [usr] at [loc_name(O.loc)]") - - use(R.req_amount * multiplier) - - if(R.applies_mats && custom_materials && custom_materials.len) - var/list/used_materials = list() - for(var/i in custom_materials) - used_materials[SSmaterials.GetMaterialRef(i)] = R.req_amount / R.res_amount * (MINERAL_MATERIAL_AMOUNT / custom_materials.len) - O.set_custom_materials(used_materials) - - //START: oh fuck i'm so sorry - if(istype(O, /obj/structure/windoor_assembly)) - var/obj/structure/windoor_assembly/W = O - W.ini_dir = W.dir - else if(istype(O, /obj/structure/window)) - var/obj/structure/window/W = O - W.ini_dir = W.dir - //END: oh fuck i'm so sorry - - else if(istype(O, /obj/item/restraints/handcuffs/cable)) - var/obj/item/cuffs = O - cuffs.color = color - - if (QDELETED(O)) - return //It's a stack and has already been merged - - if (isitem(O)) - usr.put_in_hands(O) - O.add_fingerprint(usr) - - //BubbleWrap - so newly formed boxes are empty - if ( istype(O, /obj/item/storage) ) - for (var/obj/item/I in O) - qdel(I) - //BubbleWrap END - -/obj/item/stack/proc/building_checks(datum/stack_recipe/R, multiplier) - if (get_amount() < R.req_amount*multiplier) - if (R.req_amount*multiplier>1) - to_chat(usr, "You haven't got enough [src] to build \the [R.req_amount*multiplier] [R.title]\s!") - else - to_chat(usr, "You haven't got enough [src] to build \the [R.title]!") + to_chat(usr, "You haven't got enough [src] to build \the [recipe.title]!") return FALSE - var/turf/T = get_turf(usr) + var/turf/dest_turf = get_turf(usr) - var/obj/D = R.result_type - if(R.window_checks && !valid_window_location(T, initial(D.dir) == FULLTILE_WINDOW_DIR ? FULLTILE_WINDOW_DIR : usr.dir)) - to_chat(usr, "The [R.title] won't fit here!") - return FALSE - if(R.one_per_turf && (locate(R.result_type) in T)) - to_chat(usr, "There is another [R.title] here!") - return FALSE - if(R.on_floor) - if(!isfloorturf(T)) - to_chat(usr, "\The [R.title] must be constructed on the floor!") + // If we're making a window, we have some special snowflake window checks to do. + if(ispath(recipe.result_type, /obj/structure/window)) + var/obj/structure/window/result_path = recipe.result_type + if(!valid_window_location(dest_turf, usr.dir, is_fulltile = initial(result_path.fulltile))) + to_chat(usr, "The [recipe.title] won't fit here!") return FALSE - for(var/obj/AM in T) - if(istype(AM,/obj/structure/grille)) + + if(recipe.one_per_turf && (locate(recipe.result_type) in dest_turf)) + to_chat(usr, "There is another [recipe.title] here!") + return FALSE + + if(recipe.on_floor) + if(!isfloorturf(dest_turf)) + to_chat(usr, "\The [recipe.title] must be constructed on the floor!") + return FALSE + + for(var/obj/object in dest_turf) + if(istype(object, /obj/structure/grille)) continue - if(istype(AM,/obj/structure/table)) + if(istype(object, /obj/structure/table)) continue - if(istype(AM,/obj/structure/window)) - var/obj/structure/window/W = AM - if(!W.fulltile) + if(istype(object, /obj/structure/window)) + var/obj/structure/window/window_structure = object + if(!window_structure.fulltile) continue - if(AM.density) - to_chat(usr, "Theres a [AM.name] here. You cant make a [R.title] here!") + if(object.density) + to_chat(usr, "There is \a [object.name] here. You cant make \a [recipe.title] here!") return FALSE - if(R.placement_checks) - switch(R.placement_checks) + if(recipe.placement_checks) + switch(recipe.placement_checks) if(STACK_CHECK_CARDINALS) var/turf/step for(var/direction in GLOB.cardinals) - step = get_step(T, direction) - if(locate(R.result_type) in step) - to_chat(usr, "\The [R.title] must not be built directly adjacent to another!") + step = get_step(dest_turf, direction) + if(locate(recipe.result_type) in step) + to_chat(usr, "\The [recipe.title] must not be built directly adjacent to another!") return FALSE if(STACK_CHECK_ADJACENT) - if(locate(R.result_type) in range(1, T)) - to_chat(usr, "\The [R.title] must be constructed at least one tile away from others of its type!") + if(locate(recipe.result_type) in range(1, dest_turf)) + to_chat(usr, "\The [recipe.title] must be constructed at least one tile away from others of its type!") return FALSE return TRUE @@ -330,10 +369,7 @@ if(check && zero_amount()) return TRUE if(length(mats_per_unit)) - var/temp_materials = custom_materials.Copy() - for(var/i in mats_per_unit) - temp_materials[i] = mats_per_unit[i] * src.amount - set_custom_materials(temp_materials) + update_custom_materials() update_icon() update_weight() return TRUE @@ -357,22 +393,36 @@ return source.energy < cost if(amount < 1) qdel(src) - return 1 - return 0 + return TRUE + return FALSE -/obj/item/stack/proc/add(amount) +/** Adds some number of units to this stack. + * + * Arguments: + * - _amount: The number of units to add to this stack. + */ +/obj/item/stack/proc/add(_amount) if (is_cyborg) - source.add_charge(amount * cost) + source.add_charge(_amount * cost) else - src.amount += amount + amount += _amount if(length(mats_per_unit)) - var/temp_materials = custom_materials.Copy() - for(var/i in mats_per_unit) - temp_materials[i] = mats_per_unit[i] * src.amount - set_custom_materials(temp_materials) + update_custom_materials() update_icon() update_weight() +/** Checks whether this stack can merge itself into another stack. + * + * Arguments: + * - [check][/obj/item/stack]: The stack to check for mergeability. + */ +/obj/item/stack/proc/can_merge(obj/item/stack/check) + if(!istype(check, merge_type)) + return FALSE + if(!check.is_cyborg && (mats_per_unit != check.mats_per_unit)) // Cyborg stacks don't have materials. This lets them recycle sheets and floor tiles. + return FALSE + return TRUE + /obj/item/stack/proc/merge(obj/item/stack/S) //Merge src into S, as much as possible if(QDELETED(S) || QDELETED(src) || S == src) //amusingly this can cause a stack to consume itself, let's not allow that. return @@ -388,50 +438,53 @@ S.add(transfer) return transfer -/obj/item/stack/Crossed(obj/o) - if(istype(o, merge_type) && !o.throwing) - merge(o) +/obj/item/stack/Crossed(atom/movable/crossing) + if(!crossing.throwing && can_merge(crossing)) + merge(crossing) . = ..() -/obj/item/stack/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) - if(istype(AM, merge_type)) - merge(AM) +/obj/item/stack/hitby(atom/movable/hitting, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) + if(can_merge(hitting)) + merge(hitting) . = ..() -/obj/item/stack/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags) +//ATTACK HAND IGNORING PARENT RETURN VALUE +/obj/item/stack/on_attack_hand(mob/user) if(user.get_inactive_held_item() == src) if(zero_amount()) return - return change_stack(user,1) + return split_stack(user, 1) else . = ..() /obj/item/stack/AltClick(mob/living/user) . = ..() - if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user))) + if(isturf(loc)) // to prevent people that are alt clicking a tile to see its content from getting undesidered pop ups return - if(is_cyborg) + if(is_cyborg || !user.canUseTopic(src, BE_CLOSE, TRUE, FALSE) || zero_amount()) //, !iscyborg(user) return - else - if(zero_amount()) - return - //get amount from user - var/max = get_amount() - var/stackmaterial = round(input(user,"How many sheets do you wish to take out of this stack? (Maximum [max])") as null|num) - max = get_amount() - stackmaterial = min(max, stackmaterial) - if(stackmaterial == null || stackmaterial <= 0 || !user.canUseTopic(src, BE_CLOSE, ismonkey(user))) - return TRUE - else - change_stack(user, stackmaterial) - to_chat(user, "You take [stackmaterial] sheets out of the stack") - return TRUE + //get amount from user + var/max = get_amount() + var/stackmaterial = round(input(user,"How many sheets do you wish to take out of this stack? (Maximum [max])") as null|num) + max = get_amount() + stackmaterial = min(max, stackmaterial) + if(stackmaterial == null || stackmaterial <= 0 || !user.canUseTopic(src, BE_CLOSE, TRUE, FALSE)) //, !iscyborg(user) + return + split_stack(user, stackmaterial) + to_chat(user, "You take [stackmaterial] sheets out of the stack.") -/obj/item/stack/proc/change_stack(mob/user, amount) +/** Splits the stack into two stacks. + * + * Arguments: + * - [user][/mob]: The mob splitting the stack. + * - amount: The number of units to split from this stack. + */ +/obj/item/stack/proc/split_stack(mob/user, amount) if(!use(amount, TRUE, FALSE)) - return FALSE + return null var/obj/item/stack/F = new type(user? user : drop_location(), amount, FALSE) . = F + F.set_mats_per_unit(mats_per_unit, 1) // Required for greyscale sheets and tiles. F.copy_evidences(src) if(user) if(!user.put_in_hands(F, merge_stacks = FALSE)) @@ -441,7 +494,7 @@ zero_amount() /obj/item/stack/attackby(obj/item/W, mob/user, params) - if(istype(W, merge_type)) + if(can_merge(W)) var/obj/item/stack/S = W if(merge(S)) to_chat(user, "Your [S.name] stack now contains [S.get_amount()] [S.singular_name]\s.") @@ -455,8 +508,8 @@ fingerprints = from.fingerprints.Copy() if(from.fingerprintshidden) fingerprintshidden = from.fingerprintshidden.Copy() - if(from.fingerprintslast) - fingerprintslast = from.fingerprintslast + fingerprintslast = from.fingerprintslast + //TODO bloody overlay /obj/item/stack/microwave_act(obj/machinery/microwave/M) if(istype(M) && M.dirty < 100) @@ -474,15 +527,12 @@ var/time = 0 var/one_per_turf = FALSE var/on_floor = FALSE - var/window_checks = FALSE var/placement_checks = FALSE var/applies_mats = FALSE var/trait_booster = null var/trait_modifier = 1 /datum/stack_recipe/New(title, result_type, req_amount = 1, res_amount = 1, max_res_amount = 1,time = 0, one_per_turf = FALSE, on_floor = FALSE, window_checks = FALSE, placement_checks = FALSE, applies_mats = FALSE, trait_booster = null, trait_modifier = 1) - - src.title = title src.result_type = result_type src.req_amount = req_amount @@ -491,7 +541,6 @@ src.time = time src.one_per_turf = one_per_turf src.on_floor = on_floor - src.window_checks = window_checks src.placement_checks = placement_checks src.applies_mats = applies_mats src.trait_booster = trait_booster diff --git a/code/game/objects/items/storage/boxes.dm b/code/game/objects/items/storage/boxes.dm index 3f9c3a019e..ea40b49c5f 100644 --- a/code/game/objects/items/storage/boxes.dm +++ b/code/game/objects/items/storage/boxes.dm @@ -1143,9 +1143,9 @@ desc = "A box containing a gift for worthy golems." /obj/item/storage/box/rndboards/PopulateContents() - new /obj/item/circuitboard/machine/protolathe/offstation(src) + new /obj/item/circuitboard/machine/protolathe(src) new /obj/item/circuitboard/machine/destructive_analyzer(src) - new /obj/item/circuitboard/machine/circuit_imprinter/offstation(src) + new /obj/item/circuitboard/machine/circuit_imprinter(src) new /obj/item/circuitboard/computer/rdconsole(src) /obj/item/storage/box/silver_sulf diff --git a/code/game/objects/items/storage/secure.dm b/code/game/objects/items/storage/secure.dm index 2988b54e8f..956b91929e 100644 --- a/code/game/objects/items/storage/secure.dm +++ b/code/game/objects/items/storage/secure.dm @@ -164,11 +164,15 @@ /obj/item/storage/secure/briefcase/hos/mws_pack_hos/PopulateContents() new /obj/item/gun/ballistic/revolver/mws(src) new /obj/item/ammo_box/magazine/mws_mag(src) + new /obj/item/ammo_box/magazine/mws_mag(src) new /obj/item/ammo_casing/mws_batt/lethal(src) new /obj/item/ammo_casing/mws_batt/lethal(src) + new /obj/item/ammo_casing/mws_batt/lethal(src) + new /obj/item/ammo_casing/mws_batt/stun(src) new /obj/item/ammo_casing/mws_batt/stun(src) new /obj/item/ammo_casing/mws_batt/stun(src) new /obj/item/ammo_casing/mws_batt/ion(src) + new /obj/item/ammo_casing/mws_batt/taser(src) /obj/item/storage/secure/briefcase/hos/multiphase_box name = "\improper X-01 Multiphase energy gun box" diff --git a/code/game/objects/items/tanks/tank_types.dm b/code/game/objects/items/tanks/tank_types.dm index 325e49dd7a..84f2098b06 100644 --- a/code/game/objects/items/tanks/tank_types.dm +++ b/code/game/objects/items/tanks/tank_types.dm @@ -58,7 +58,6 @@ /obj/item/tank/internals/air name = "air tank" desc = "Mixed anyone?" - icon_state = "air" item_state = "air" force = 10 dog_fashion = /datum/dog_fashion/back diff --git a/code/game/objects/items/tanks/tanks.dm b/code/game/objects/items/tanks/tanks.dm index d57f0cc51f..746b25facb 100644 --- a/code/game/objects/items/tanks/tanks.dm +++ b/code/game/objects/items/tanks/tanks.dm @@ -1,10 +1,12 @@ /obj/item/tank name = "tank" icon = 'icons/obj/tank.dmi' + icon_state = "generic" lefthand_file = 'icons/mob/inhands/equipment/tanks_lefthand.dmi' righthand_file = 'icons/mob/inhands/equipment/tanks_righthand.dmi' flags_1 = CONDUCT_1 slot_flags = ITEM_SLOT_BACK + // worn_icon = 'icons/mob/clothing/back.dmi' //since these can also get thrown into suit storage slots. if something goes on the belt, set this to null. hitsound = 'sound/weapons/smash.ogg' pressure_resistance = ONE_ATMOSPHERE * 5 force = 5 @@ -18,6 +20,8 @@ var/distribute_pressure = ONE_ATMOSPHERE var/integrity = 3 var/volume = 70 + /// Icon state when in a tank holder. Null makes it incompatible with tank holder. + var/tank_holder_icon_state = "holder_generic" /obj/item/tank/ui_action_click(mob/user) toggle_internals(user) @@ -84,18 +88,23 @@ /obj/item/tank/Destroy() if(air_contents) - qdel(air_contents) + QDEL_NULL(air_contents) STOP_PROCESSING(SSobj, src) . = ..() +// /obj/item/tank/ComponentInitialize() +// . = ..() +// if(tank_holder_icon_state) +// AddComponent(/datum/component/container_item/tank_holder, tank_holder_icon_state) + /obj/item/tank/examine(mob/user) var/obj/icon = src . = ..() if(istype(src.loc, /obj/item/assembly)) icon = src.loc if(!in_range(src, user) && !isobserver(user)) - if (icon == src) + if(icon == src) . += "If you want any more information you'll need to get closer." return @@ -140,14 +149,14 @@ if(T) T.assume_air(air_contents) air_update_turf() - playsound(src.loc, 'sound/effects/spray.ogg', 10, 1, -3) + playsound(src.loc, 'sound/effects/spray.ogg', 10, TRUE, -3) qdel(src) /obj/item/tank/suicide_act(mob/user) var/mob/living/carbon/human/H = user user.visible_message("[user] is putting [src]'s valve to [user.p_their()] lips! It looks like [user.p_theyre()] trying to commit suicide!") - playsound(loc, 'sound/effects/spray.ogg', 10, 1, -3) - if (!QDELETED(H) && air_contents && air_contents.return_pressure() >= 1000) + playsound(loc, 'sound/effects/spray.ogg', 10, TRUE, -3) + if(!QDELETED(H) && air_contents && air_contents.return_pressure() >= 1000) for(var/obj/item/W in H) H.dropItemToGround(W) if(prob(50)) @@ -159,12 +168,10 @@ H.spawn_gibs() H.spill_organs() H.spread_bodyparts() - - return (BRUTELOSS) - -/obj/item/tank/attack_ghost(mob/dead/observer/O) - . = ..() - atmosanalyzer_scan(air_contents, O, src, FALSE) + return MANUAL_SUICIDE + else + to_chat(user, "There isn't enough pressure in [src] to commit suicide with...") + return SHAME /obj/item/tank/attackby(obj/item/W, mob/user, params) add_fingerprint(user) @@ -182,27 +189,30 @@ ui = new(user, src, "Tank", name) ui.open() +/obj/item/tank/ui_static_data(mob/user) + . = list ( + "defaultReleasePressure" = round(TANK_DEFAULT_RELEASE_PRESSURE), + "minReleasePressure" = round(TANK_MIN_RELEASE_PRESSURE), + "maxReleasePressure" = round(TANK_MAX_RELEASE_PRESSURE), + "leakPressure" = round(TANK_LEAK_PRESSURE), + "fragmentPressure" = round(TANK_FRAGMENT_PRESSURE) + ) + /obj/item/tank/ui_data(mob/user) - var/list/data = list() - data["tankPressure"] = round(air_contents.return_pressure() ? air_contents.return_pressure() : 0) - data["releasePressure"] = round(distribute_pressure ? distribute_pressure : 0) - data["defaultReleasePressure"] = round(TANK_DEFAULT_RELEASE_PRESSURE) - data["minReleasePressure"] = round(TANK_MIN_RELEASE_PRESSURE) - data["maxReleasePressure"] = round(TANK_MAX_RELEASE_PRESSURE) + . = list( + "tankPressure" = round(air_contents.return_pressure()), + "releasePressure" = round(distribute_pressure) + ) var/mob/living/carbon/C = user if(!istype(C)) C = loc.loc - if(!istype(C)) - return data - - if(C.internal == src) - data["connected"] = TRUE - - return data + if(istype(C) && C.internal == src) + .["connected"] = TRUE /obj/item/tank/ui_act(action, params) - if(..()) + . = ..() + if(.) return switch(action) if("pressure") @@ -228,6 +238,9 @@ /obj/item/tank/return_air() return air_contents +// /obj/item/tank/return_analyzable_air() +// return air_contents + /obj/item/tank/assume_air(datum/gas_mixture/giver) air_contents.merge(giver) @@ -239,10 +252,9 @@ return null var/tank_pressure = air_contents.return_pressure() - if(tank_pressure < distribute_pressure) - distribute_pressure = tank_pressure + var/actual_distribute_pressure = clamp(tank_pressure, 0, distribute_pressure) - var/moles_needed = distribute_pressure*volume_to_return/(R_IDEAL_GAS_EQUATION*air_contents.return_temperature()) + var/moles_needed = actual_distribute_pressure*volume_to_return/(R_IDEAL_GAS_EQUATION*air_contents.return_temperature()) return remove_air(moles_needed) @@ -267,7 +279,7 @@ //Give the gas a chance to build up more pressure through reacting air_contents.react(src) air_contents.react(src) - //Citadel Edit: removing extra react for "balance" + pressure = air_contents.return_pressure() var/range = (pressure-TANK_FRAGMENT_PRESSURE)/TANK_FRAGMENT_SCALE var/turf/epicenter = get_turf(loc) @@ -285,7 +297,7 @@ if(!T) return T.assume_air(air_contents) - playsound(src.loc, 'sound/effects/spray.ogg', 10, 1, -3) + playsound(src.loc, 'sound/effects/spray.ogg', 10, TRUE, -3) qdel(src) else integrity-- diff --git a/code/game/objects/items/weaponry.dm b/code/game/objects/items/weaponry.dm index a826900871..a3415f2166 100644 --- a/code/game/objects/items/weaponry.dm +++ b/code/game/objects/items/weaponry.dm @@ -24,9 +24,9 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 */ /obj/item/banhammer/attack(mob/M, mob/user) if(user.zone_selected == BODY_ZONE_HEAD) - M.visible_message("[user] are stroking the head of [M] with a bangammer", "[user] are stroking the head with a bangammer", "you hear a bangammer stroking a head"); + M.visible_message("[user] are stroking the head of [M] with a bangammer.", "[user] are stroking your head with a bangammer.", "You hear a bangammer stroking a head.") // see above comment else - M.visible_message("[M] has been banned FOR NO REISIN by [user]", "You have been banned FOR NO REISIN by [user]", "you hear a banhammer banning someone") + M.visible_message("[M] has been banned FOR NO REISIN by [user]!", "You have been banned FOR NO REISIN by [user]!", "You hear a banhammer banning someone.") playsound(loc, 'sound/effects/adminhelp.ogg', 15) //keep it at 15% volume so people don't jump out of their skin too much if(user.a_intent != INTENT_HELP) return ..(M, user) @@ -89,7 +89,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 /obj/item/claymore/highlander //ALL COMMENTS MADE REGARDING THIS SWORD MUST BE MADE IN ALL CAPS desc = "THERE CAN BE ONLY ONE, AND IT WILL BE YOU!!!\nActivate it in your hand to point to the nearest victim." flags_1 = CONDUCT_1 - item_flags = DROPDEL + item_flags = DROPDEL //WOW BRO YOU LOST AN ARM, GUESS WHAT YOU DONT GET YOUR SWORD ANYMORE //I CANT BELIEVE SPOOKYDONUT WOULD BREAK THE REQUIREMENTS slot_flags = null block_chance = 0 //RNG WON'T HELP YOU NOW, PANSY light_range = 3 @@ -130,8 +130,6 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 /obj/item/claymore/highlander/dropped(mob/living/user) . = ..() user.unignore_slowdown(HIGHLANDER) - if(!QDELETED(src)) - qdel(src) //If this ever happens, it's because you lost an arm /obj/item/claymore/highlander/examine(mob/user) . = ..() @@ -141,8 +139,8 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 /obj/item/claymore/highlander/attack(mob/living/target, mob/living/user) . = ..() - if(!QDELETED(target) && iscarbon(target) && target.stat == DEAD && target.mind && target.mind.special_role == "highlander") - user.fully_heal() //STEAL THE LIFE OF OUR FALLEN FOES + if(!QDELETED(target) && target.stat == DEAD && target.mind && target.mind.special_role == "highlander") + user.fully_heal(admin_revive = FALSE) //STEAL THE LIFE OF OUR FALLEN FOES add_notch(user) target.visible_message("[target] crumbles to dust beneath [user]'s blows!", "As you fall, your body crumbles to dust!") target.dust() @@ -150,9 +148,13 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 /obj/item/claymore/highlander/attack_self(mob/living/user) var/closest_victim var/closest_distance = 255 - for(var/mob/living/carbon/human/H in GLOB.player_list - user) - if(H.client && H.mind.special_role == "highlander" && (!closest_victim || get_dist(user, closest_victim) < closest_distance)) - closest_victim = H + for(var/mob/living/carbon/human/scot in GLOB.player_list - user) + if(scot.mind.special_role == "highlander" && (!closest_victim || get_dist(user, closest_victim) < closest_distance)) + closest_victim = scot + for(var/mob/living/silicon/robot/siliscot in GLOB.player_list - user) + if(siliscot.mind.special_role == "highlander" && (!closest_victim || get_dist(user, closest_victim) < closest_distance)) + closest_victim = siliscot + if(!closest_victim) to_chat(user, "[src] thrums for a moment and falls dark. Perhaps there's nobody nearby.") return @@ -161,7 +163,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 /obj/item/claymore/highlander/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) if((attack_type & ATTACK_TYPE_PROJECTILE) && is_energy_reflectable_projectile(object)) return BLOCK_SUCCESS | BLOCK_SHOULD_REDIRECT | BLOCK_PHYSICAL_EXTERNAL | BLOCK_REDIRECTED - return ..() + return ..() //YOU THINK YOUR PUNY LASERS CAN STOP ME? /obj/item/claymore/highlander/proc/add_notch(mob/living/user) //DYNAMIC CLAYMORE PROGRESSION SYSTEM - THIS IS THE FUTURE notches++ @@ -214,7 +216,22 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 remove_atom_colour(ADMIN_COLOUR_PRIORITY) name = new_name - playsound(user, 'sound/items/screwdriver2.ogg', 50, 1) + playsound(user, 'sound/items/screwdriver2.ogg', 50, TRUE) + +/obj/item/claymore/highlander/robot //BLOODTHIRSTY BORGS NOW COME IN PLAID + icon = 'icons/obj/items_cyborg.dmi' + icon_state = "claymore_cyborg" + var/mob/living/silicon/robot/robot + +/obj/item/claymore/highlander/robot/Initialize() + var/obj/item/robot_module/kiltkit = loc + robot = kiltkit.loc + if(!istype(robot)) + qdel(src) + return ..() + +/obj/item/claymore/highlander/robot/process() + loc.layer = LARGE_MOB_LAYER /obj/item/katana name = "katana" @@ -227,7 +244,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 slot_flags = ITEM_SLOT_BELT | ITEM_SLOT_BACK force = 40 throwforce = 10 - w_class = WEIGHT_CLASS_BULKY + w_class = WEIGHT_CLASS_HUGE hitsound = 'sound/weapons/bladeslice.ogg' attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") block_chance = 50 @@ -581,7 +598,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 user.put_in_hands(S) to_chat(user, "You fasten the glass shard to the top of the rod with the cable.") - else if(istype(I, /obj/item/assembly/igniter) && !HAS_TRAIT(I, TRAIT_NODROP)) + else if(istype(I, /obj/item/assembly/igniter) && !(HAS_TRAIT(I, TRAIT_NODROP))) var/obj/item/melee/baton/cattleprod/P = new /obj/item/melee/baton/cattleprod remove_item_from_storage(user) @@ -604,13 +621,13 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 lefthand_file = 'icons/mob/inhands/equipment/shields_lefthand.dmi' righthand_file = 'icons/mob/inhands/equipment/shields_righthand.dmi' force = 2 - throwforce = 10 //This is never used on mobs since this has a 100% embed chance. + throwforce = 10 //10 + 2 (WEIGHT_CLASS_SMALL) * 4 (EMBEDDED_IMPACT_PAIN_MULTIPLIER) = 18 damage on hit due to guaranteed embedding throw_speed = 4 embedding = list("pain_mult" = 4, "embed_chance" = 100, "fall_chance" = 0, "embed_chance_turf_mod" = 15) armour_penetration = 40 w_class = WEIGHT_CLASS_SMALL - sharpness = SHARP_EDGED + sharpness = SHARP_POINTY custom_materials = list(/datum/material/iron=500, /datum/material/glass=500) resistance_flags = FIRE_PROOF @@ -645,27 +662,23 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 attack_verb = list("stubbed", "poked") resistance_flags = FIRE_PROOF var/extended = 0 - var/extended_force = 20 - var/extended_throwforce = 23 - var/extended_icon_state = "switchblade_ext" - var/retracted_icon_state = "switchblade" /obj/item/switchblade/attack_self(mob/user) extended = !extended - playsound(src.loc, 'sound/weapons/batonextend.ogg', 50, 1) + playsound(src.loc, 'sound/weapons/batonextend.ogg', 50, TRUE) if(extended) - force = extended_force + force = 20 w_class = WEIGHT_CLASS_NORMAL - throwforce = extended_throwforce - icon_state = extended_icon_state + throwforce = 23 + icon_state = "switchblade_ext" attack_verb = list("slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") hitsound = 'sound/weapons/bladeslice.ogg' sharpness = SHARP_EDGED else - force = initial(force) + force = 3 w_class = WEIGHT_CLASS_SMALL - throwforce = initial(throwforce) - icon_state = retracted_icon_state + throwforce = 5 + icon_state = "switchblade" attack_verb = list("stubbed", "poked") hitsound = 'sound/weapons/genhit.ogg' sharpness = SHARP_NONE @@ -756,6 +769,10 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 user.visible_message("[user] is inhaling [src]! It looks like [user.p_theyre()] trying to visit the astral plane!") return (OXYLOSS) +// /obj/item/ectoplasm/angelic +// icon = 'icons/obj/wizard.dmi' +// icon_state = "angelplasm" + /obj/item/mounted_chainsaw name = "mounted chainsaw" desc = "A chainsaw that has replaced your arm." @@ -764,7 +781,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 lefthand_file = 'icons/mob/inhands/weapons/chainsaw_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/chainsaw_righthand.dmi' item_flags = ABSTRACT | DROPDEL - w_class = WEIGHT_CLASS_BULKY + w_class = WEIGHT_CLASS_HUGE force = 24 throwforce = 0 throw_range = 0 @@ -807,7 +824,13 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 /obj/item/statuebust/Initialize() . = ..() AddElement(/datum/element/art, impressiveness) - addtimer(CALLBACK(src, /datum.proc/_AddElement, list(/datum/element/beauty, 1000)), 0) + // AddComponent(/datum/component/beauty, 1000) + +// /obj/item/statuebust/hippocratic +// name = "hippocrates bust" +// desc = "A bust of the famous Greek physician Hippocrates of Kos, often referred to as the father of western medicine." +// icon_state = "hippocratic" +// impressiveness = 50 /obj/item/tailclub name = "tail club" @@ -831,8 +854,8 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 icon_state = "catwhip" /obj/item/melee/skateboard - name = "improvised skateboard" - desc = "A skateboard. It can be placed on its wheels and ridden, or used as a strong weapon." + name = "skateboard" + desc = "A skateboard. It can be placed on its wheels and ridden, or used as a radical weapon." icon_state = "skateboard" item_state = "skateboard" force = 12 @@ -845,17 +868,22 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 /obj/item/melee/skateboard/attack_self(mob/user) if(!user.canUseTopic(src, TRUE, FALSE, TRUE)) return - var/obj/vehicle/ridden/scooter/skateboard/S = new board_item_type(get_turf(user)) + var/obj/vehicle/ridden/scooter/skateboard/S = new board_item_type(get_turf(user))//this probably has fucky interactions with telekinesis but for the record it wasn't my fault S.buckle_mob(user) qdel(src) +/obj/item/melee/skateboard/improvised + name = "improvised skateboard" + desc = "A jury-rigged skateboard. It can be placed on its wheels and ridden, or used as a radical weapon." + // board_item_type = /obj/vehicle/ridden/scooter/skateboard/improvised + /obj/item/melee/skateboard/pro name = "skateboard" - desc = "A RaDSTORMz brand professional skateboard. It looks sturdy and well made." + desc = "An EightO brand professional skateboard. It looks sturdy and well made." icon_state = "skateboard2" item_state = "skateboard2" board_item_type = /obj/vehicle/ridden/scooter/skateboard/pro - custom_premium_price = 500 + custom_premium_price = PAYCHECK_HARD * 5 /obj/item/melee/skateboard/hoverboard name = "hoverboard" @@ -863,10 +891,10 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 icon_state = "hoverboard_red" item_state = "hoverboard_red" board_item_type = /obj/vehicle/ridden/scooter/skateboard/hoverboard - custom_premium_price = 2015 + custom_premium_price = PAYCHECK_COMMAND * 5.4 //If I can't make it a meme I'll make it RAD /obj/item/melee/skateboard/hoverboard/admin - name = "\improper Board Of Directors" + name = "Board Of Directors" desc = "The engineering complexity of a spaceship concentrated inside of a board. Just as expensive, too." icon_state = "hoverboard_nt" item_state = "hoverboard_nt" @@ -885,11 +913,19 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 throwforce = 12 attack_verb = list("beat", "smacked") custom_materials = list(/datum/material/wood = MINERAL_MATERIAL_AMOUNT * 3.5) - w_class = WEIGHT_CLASS_BULKY + w_class = WEIGHT_CLASS_HUGE var/homerun_ready = 0 var/homerun_able = 0 total_mass = 2.7 //a regular wooden major league baseball bat weighs somewhere between 2 to 3.4 pounds, according to google +/obj/item/melee/baseball_bat/Initialize() + . = ..() + if(prob(1)) + name = "cricket bat" + desc = "You've got red on you." + icon_state = "baseball_bat_brit" + item_state = "baseball_bat_brit" + /obj/item/melee/baseball_bat/chaplain name = "blessed baseball bat" desc = "There ain't a cult in the league that can withstand a swatter." @@ -914,11 +950,11 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 ..() return if(homerun_ready) - to_chat(user, "You're already ready to do a home run!") + to_chat(user, "You're already ready to do a home run!") ..() return to_chat(user, "You begin gathering strength...") - playsound(get_turf(src), 'sound/magic/lightning_chargeup.ogg', 65, 1) + playsound(get_turf(src), 'sound/magic/lightning_chargeup.ogg', 65, TRUE) if(do_after(user, 90, target = src)) to_chat(user, "You gather power! Time for a home run!") homerun_ready = 1 @@ -926,12 +962,14 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 /obj/item/melee/baseball_bat/attack(mob/living/target, mob/living/user) . = ..() + if(HAS_TRAIT(user, TRAIT_PACIFISM)) + return var/atom/throw_target = get_edge_target_turf(target, user.dir) if(homerun_ready) user.visible_message("It's a home run!") target.throw_at(throw_target, rand(8,10), 14, user) target.ex_act(EXPLODE_HEAVY) - playsound(get_turf(src), 'sound/weapons/homerun.ogg', 100, 1) + playsound(get_turf(src), 'sound/weapons/homerun.ogg', 100, TRUE) homerun_ready = 0 return else if(!target.anchored) @@ -962,7 +1000,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 /obj/item/melee/flyswatter name = "flyswatter" - desc = "Useful for killing insects of all sizes." + desc = "Useful for killing pests of all sizes." icon = 'icons/obj/items_and_weapons.dmi' icon_state = "flyswatter" item_state = "flyswatter" @@ -975,7 +1013,6 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 w_class = WEIGHT_CLASS_SMALL //Things in this list will be instantly splatted. Flyman weakness is handled in the flyman species weakness proc. var/list/strong_against - var/list/spider_panic /obj/item/melee/flyswatter/Initialize() . = ..() @@ -983,13 +1020,11 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 /mob/living/simple_animal/hostile/poison/bees/, /mob/living/simple_animal/butterfly, /mob/living/simple_animal/cockroach, - /obj/item/queen_bee - )) - spider_panic = typecacheof(list( - /mob/living/simple_animal/banana_spider, - /mob/living/simple_animal/hostile/poison/giant_spider, + /obj/item/queen_bee, + /obj/structure/spider/spiderling )) + /obj/item/melee/flyswatter/afterattack(atom/target, mob/user, proximity_flag) . = ..() if(proximity_flag) @@ -999,11 +1034,6 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 if(istype(target, /mob/living/)) var/mob/living/bug = target bug.death(1) - if(is_type_in_typecache(target, spider_panic)) - to_chat(user, "You easily land a critical blow on the [target].") - if(istype(target, /mob/living/)) - var/mob/living/bug = target - bug.adjustBruteLoss(35) //What kinda mad man would go into melee with a spider?! else qdel(target) @@ -1013,7 +1043,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 icon_state = "madeyoulook" force = 0 throwforce = 0 - item_flags = DROPDEL | ABSTRACT + item_flags = DROPDEL | ABSTRACT // | HAND_ITEM attack_verb = list("bopped") /obj/item/circlegame/Initialize() @@ -1036,6 +1066,8 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 /// Stage 1: The mistake is made /obj/item/circlegame/proc/ownerExamined(mob/living/owner, mob/living/sucker) + SIGNAL_HANDLER + if(!istype(sucker) || !in_range(owner, sucker)) return addtimer(CALLBACK(src, .proc/waitASecond, owner, sucker), 4) @@ -1082,6 +1114,10 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 to_chat(owner, "[sucker] looks down at your [src.name] before trying to avert [sucker.p_their()] eyes, but it's too late!") to_chat(sucker, "[owner] sees the fear in your eyes as you try to look away from [owner.p_their()] [src.name]!") + owner.face_atom(sucker) + if(owner.client) + owner.client.give_award(/datum/award/achievement/misc/gottem, owner) // then everybody clapped + playsound(get_turf(owner), 'sound/effects/hit_punch.ogg', 50, TRUE, -1) owner.do_attack_animation(sucker) @@ -1109,7 +1145,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 item_state = "nothing" force = 0 throwforce = 0 - item_flags = DROPDEL | ABSTRACT + item_flags = DROPDEL | ABSTRACT // | HAND_ITEM attack_verb = list("slapped") hitsound = 'sound/effects/snap.ogg' @@ -1118,16 +1154,12 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 var/mob/living/carbon/human/L = M if(L && L.dna && L.dna.species) L.dna.species.stop_wagging_tail(M) - if(user.a_intent != INTENT_HARM && ((user.zone_selected == BODY_ZONE_PRECISE_MOUTH) || (user.zone_selected == BODY_ZONE_PRECISE_EYES) || (user.zone_selected == BODY_ZONE_HEAD))) - user.do_attack_animation(M) - playsound(M, 'sound/weapons/slap.ogg', 50, 1, -1) - user.visible_message("[user] slaps [M]!", - "You slap [M]!",\ - "You hear a slap.") - return - else - ..() - + user.do_attack_animation(M) + playsound(M, 'sound/weapons/slap.ogg', 50, TRUE, -1) + user.visible_message("[user] slaps [M]!", + "You slap [M]!",\ + "You hear a slap.") + return /obj/item/proc/can_trigger_gun(mob/living/user) if(!user.can_use_guns(src)) return FALSE @@ -1144,6 +1176,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 force = 0 throwforce = 5 reach = 2 + var/min_reach = 2 /obj/item/extendohand/acme name = "\improper ACME Extendo-Hand" @@ -1151,13 +1184,26 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 /obj/item/extendohand/attack(atom/M, mob/living/carbon/human/user) var/dist = get_dist(M, user) - if(dist < reach) + if(dist < min_reach) to_chat(user, "[M] is too close to use [src] on.") return M.attack_hand(user) -//HF blade +// /obj/item/gohei +// name = "gohei" +// desc = "A wooden stick with white streamers at the end. Originally used by shrine maidens to purify things. Now used by the station's valued weeaboos." +// force = 5 +// throwforce = 5 +// hitsound = "swing_hit" +// attack_verb_continuous = list("whacks", "thwacks", "wallops", "socks") +// attack_verb_simple = list("whack", "thwack", "wallop", "sock") +// icon = 'icons/obj/items_and_weapons.dmi' +// icon_state = "gohei" +// inhand_icon_state = "gohei" +// lefthand_file = 'icons/mob/inhands/weapons/staves_lefthand.dmi' +// righthand_file = 'icons/mob/inhands/weapons/staves_righthand.dmi' +//HF blade /obj/item/vibro_weapon icon_state = "hfrequency0" lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' @@ -1166,6 +1212,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 desc = "A potent weapon capable of cutting through nearly anything. Wielding it in two hands will allow you to deflect gunfire." armour_penetration = 100 block_chance = 40 + force = 20 throwforce = 20 throw_speed = 4 sharpness = SHARP_EDGED @@ -1188,10 +1235,14 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 /// triggered on wield of two handed item /obj/item/vibro_weapon/proc/on_wield(obj/item/source, mob/user) + SIGNAL_HANDLER + wielded = TRUE /// triggered on unwield of two handed item /obj/item/vibro_weapon/proc/on_unwield(obj/item/source, mob/user) + SIGNAL_HANDLER + wielded = FALSE /obj/item/vibro_weapon/update_icon_state() diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm index 8a14d99776..f0edf7ee1d 100644 --- a/code/game/objects/objs.dm +++ b/code/game/objects/objs.dm @@ -80,9 +80,9 @@ SStgui.close_uis(src) . = ..() +/// @depricated DO NOT USE /obj/proc/setAnchored(anchorvalue) - SEND_SIGNAL(src, COMSIG_OBJ_SETANCHORED, anchorvalue) - anchored = anchorvalue + set_anchored(anchorvalue) /obj/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, force, messy_throw = TRUE) . = ..() @@ -344,11 +344,6 @@ if(resistance_flags & ON_FIRE) . += GLOB.fire_overlay -//Called when the object is constructed by an autolathe -//Has a reference to the autolathe so you can do !!FUN!! things with hacked lathes -/obj/proc/autolathe_crafted(obj/machinery/autolathe/A) - return - /obj/proc/rnd_crafted(obj/machinery/rnd/production/P) return diff --git a/code/game/objects/structures/artstuff.dm b/code/game/objects/structures/artstuff.dm index 47584a1775..9b2f305b42 100644 --- a/code/game/objects/structures/artstuff.dm +++ b/code/game/objects/structures/artstuff.dm @@ -16,12 +16,12 @@ //Adding canvases /obj/structure/easel/attackby(obj/item/I, mob/user, params) if(istype(I, /obj/item/canvas)) - var/obj/item/canvas/C = I - user.dropItemToGround(C) - painting = C - C.forceMove(get_turf(src)) - C.layer = layer+0.1 - user.visible_message("[user] puts \the [C] on \the [src].","You place \the [C] on \the [src].") + var/obj/item/canvas/canvas = I + user.dropItemToGround(canvas) + painting = canvas + canvas.forceMove(get_turf(src)) + canvas.layer = layer+0.1 + user.visible_message("[user] puts \the [canvas] on \the [src].","You place \the [canvas] on \the [src].") else return ..() @@ -40,13 +40,14 @@ desc = "Draw out your soul on this canvas!" icon = 'icons/obj/artstuff.dmi' icon_state = "11x11" + // flags_1 = UNPAINTABLE_1 resistance_flags = FLAMMABLE var/width = 11 var/height = 11 var/list/grid var/canvas_color = "#ffffff" //empty canvas color var/used = FALSE - var/painting_name //Painting name, this is set after framing. + var/painting_name = "Untitled Artwork" //Painting name, this is set after framing. var/finalized = FALSE //Blocks edits var/author_ckey var/icon_generated = FALSE @@ -132,17 +133,19 @@ /obj/item/canvas/update_overlays() . = ..() - if(!icon_generated) - if(used) - var/mutable_appearance/detail = mutable_appearance(icon,"[icon_state]wip") - detail.pixel_x = 1 - detail.pixel_y = 1 - . += detail - else + if(icon_generated) var/mutable_appearance/detail = mutable_appearance(generated_icon) detail.pixel_x = 1 detail.pixel_y = 1 . += detail + return + if(!used) + return + + var/mutable_appearance/detail = mutable_appearance(icon, "[icon_state]wip") + detail.pixel_x = 1 + detail.pixel_y = 1 + . += detail /obj/item/canvas/proc/generate_proper_overlay() if(icon_generated) @@ -167,8 +170,8 @@ if(!I) return if(istype(I, /obj/item/toy/crayon)) - var/obj/item/toy/crayon/C = I - return C.paint_color + var/obj/item/toy/crayon/crayon = I + return crayon.paint_color else if(istype(I, /obj/item/pen)) var/obj/item/pen/P = I switch(P.colour) @@ -184,7 +187,7 @@ /obj/item/canvas/proc/try_rename(mob/user) var/new_name = stripped_input(user,"What do you want to name the painting?") - if(!painting_name && new_name && user.canUseTopic(src,BE_CLOSE)) + if(new_name != painting_name && new_name && user.canUseTopic(src,BE_CLOSE)) painting_name = new_name SStgui.update_uis(src) @@ -215,12 +218,23 @@ framed_offset_x = 5 framed_offset_y = 6 +/obj/item/canvas/twentyfour_twentyfour + name = "ai universal standard canvas" + desc = "Besides being very large, the AI can accept these as a display from their internal database after you've hung it up." + icon_state = "24x24" + width = 24 + height = 24 + pixel_x = 2 + pixel_y = 1 + framed_offset_x = 4 + framed_offset_y = 5 + /obj/item/wallframe/painting name = "painting frame" desc = "The perfect showcase for your favorite deathtrap memories." icon = 'icons/obj/decals.dmi' - custom_materials = null - flags_1 = 0 + custom_materials = list(/datum/material/wood = 2000) + flags_1 = NONE icon_state = "frame-empty" result_path = /obj/structure/sign/painting @@ -229,8 +243,13 @@ desc = "Art or \"Art\"? You decide." icon = 'icons/obj/decals.dmi' icon_state = "frame-empty" + // base_icon_state = "frame" + custom_materials = list(/datum/material/wood = 2000) buildable_sign = FALSE - var/obj/item/canvas/C + ///Canvas we're currently displaying. + var/obj/item/canvas/current_canvas + ///Description set when canvas is added. + var/desc_with_canvas var/persistence_id /obj/structure/sign/painting/Initialize(mapload, dir, building) @@ -242,64 +261,78 @@ if(building) pixel_x = (dir & 3)? 0 : (dir == 4 ? -30 : 30) pixel_y = (dir & 3)? (dir ==1 ? -30 : 30) : 0 + desc = current_canvas ? desc_with_canvas : initial(desc) /obj/structure/sign/painting/Destroy() . = ..() SSpersistence.painting_frames -= src /obj/structure/sign/painting/attackby(obj/item/I, mob/user, params) - if(!C && istype(I, /obj/item/canvas)) + if(!current_canvas && istype(I, /obj/item/canvas)) frame_canvas(user,I) - else if(C && !C.painting_name && istype(I,/obj/item/pen)) + else if(current_canvas && current_canvas.painting_name == initial(current_canvas.painting_name) && istype(I,/obj/item/pen)) try_rename(user) else return ..() /obj/structure/sign/painting/examine(mob/user) . = ..() - if(C) - C.ui_interact(user) + if(persistence_id) + . += "Any painting placed here will be archived at the end of the shift." + if(current_canvas) + current_canvas.ui_interact(user) + . += "Use wirecutters to remove the painting." /obj/structure/sign/painting/wirecutter_act(mob/living/user, obj/item/I) . = ..() - if(C) - C.forceMove(drop_location()) - C = null + if(current_canvas) + current_canvas.forceMove(drop_location()) + current_canvas = null to_chat(user, "You remove the painting from the frame.") update_icon() return TRUE /obj/structure/sign/painting/proc/frame_canvas(mob/user,obj/item/canvas/new_canvas) if(user.transferItemToLoc(new_canvas,src)) - C = new_canvas - if(!C.finalized) - C.finalize(user) - to_chat(user,"You frame [C].") + current_canvas = new_canvas + if(!current_canvas.finalized) + current_canvas.finalize(user) + to_chat(user,"You frame [current_canvas].") update_icon() /obj/structure/sign/painting/proc/try_rename(mob/user) - if(!C.painting_name) - C.try_rename(user) + if(current_canvas.painting_name == initial(current_canvas.painting_name)) + current_canvas.try_rename(user) + +// /obj/structure/sign/painting/update_name(updates) +// name = current_canvas ? "painting - [current_canvas.painting_name]" : initial(name) +// return ..() + +// /obj/structure/sign/painting/update_desc(updates) +// desc = current_canvas ? desc_with_canvas : initial(desc) +// return ..() /obj/structure/sign/painting/update_icon_state() - . = ..() - if(C && C.generated_icon) - icon_state = null - else + // icon_state = "[base_icon_state]-[current_canvas?.generated_icon ? "overlay" : "empty"]" + if(current_canvas?.generated_icon) icon_state = "frame-empty" - + else + icon_state = null // or "frame-empty" + return ..() /obj/structure/sign/painting/update_overlays() . = ..() - if(C && C.generated_icon) - var/mutable_appearance/MA = mutable_appearance(C.generated_icon) - MA.pixel_x = C.framed_offset_x - MA.pixel_y = C.framed_offset_y - . += MA - var/mutable_appearance/frame = mutable_appearance(C.icon,"[C.icon_state]frame") - frame.pixel_x = C.framed_offset_x - 1 - frame.pixel_y = C.framed_offset_y - 1 - . += frame + if(!current_canvas?.generated_icon) + return + + var/mutable_appearance/MA = mutable_appearance(current_canvas.generated_icon) + MA.pixel_x = current_canvas.framed_offset_x + MA.pixel_y = current_canvas.framed_offset_y + . += MA + var/mutable_appearance/frame = mutable_appearance(current_canvas.icon,"[current_canvas.icon_state]frame") + frame.pixel_x = current_canvas.framed_offset_x - 1 + frame.pixel_y = current_canvas.framed_offset_y - 1 + . += frame /obj/structure/sign/painting/proc/load_persistent() if(!persistence_id) @@ -310,6 +343,10 @@ var/title = chosen["title"] var/author = chosen["ckey"] var/png = "data/paintings/[persistence_id]/[chosen["md5"]].png" + if(!title) + title = "Untitled Artwork" //Should prevent NULL named art from loading as NULL, if you're still getting the admin log chances are persistence is broken + if(!title) + message_admins("Painting with NO TITLE loaded on a [persistence_id] frame in [get_area(src)]. Please delete it, it is saved in the database with no name and will create bad assets.") if(!fexists(png)) stack_trace("Persistent painting [chosen["md5"]].png was not found in [persistence_id] directory.") return @@ -328,17 +365,20 @@ new_canvas.finalized = TRUE new_canvas.painting_name = title new_canvas.author_ckey = author - C = new_canvas + new_canvas.name = "painting - [title]" + current_canvas = new_canvas update_icon() /obj/structure/sign/painting/proc/save_persistent() - if(!persistence_id || !C) + if(!persistence_id || !current_canvas) return if(sanitize_filename(persistence_id) != persistence_id) stack_trace("Invalid persistence_id - [persistence_id]") return - var/data = C.get_data_string() - var/md5 = md5(data) + if(!current_canvas.painting_name) + current_canvas.painting_name = "Untitled Artwork" + var/data = current_canvas.get_data_string() + var/md5 = md5(lowertext(data)) var/list/current = SSpersistence.paintings[persistence_id] if(!current) current = list() @@ -347,10 +387,10 @@ return var/png_directory = "data/paintings/[persistence_id]/" var/png_path = png_directory + "[md5].png" - var/result = rustg_dmi_create_png(png_path,"[C.width]","[C.height]",data) + var/result = rustg_dmi_create_png(png_path,"[current_canvas.width]","[current_canvas.height]",data) if(result) CRASH("Error saving persistent painting: [result]") - current += list(list("title" = C.painting_name , "md5" = md5, "ckey" = C.author_ckey)) + current += list(list("title" = current_canvas.painting_name , "md5" = md5, "ckey" = current_canvas.author_ckey)) SSpersistence.paintings[persistence_id] = current /obj/item/canvas/proc/fill_grid_from_icon(icon/I) @@ -361,12 +401,21 @@ //Presets for art gallery mapping, for paintings to be shared across stations /obj/structure/sign/painting/library + name = "\improper Public Painting Exhibit mounting" + desc = "For art pieces hung by the public." + desc_with_canvas = "A piece of art (or \"art\"). Anyone could've hung it." persistence_id = "library" /obj/structure/sign/painting/library_secure + name = "\improper Curated Painting Exhibit mounting" + desc = "For masterpieces hand-picked by the curator." + desc_with_canvas = "A masterpiece hand-picked by the curator, supposedly." persistence_id = "library_secure" /obj/structure/sign/painting/library_private // keep your smut away from prying eyes, or non-librarians at least + name = "\improper Private Painting Exhibit mounting" + desc = "For art pieces deemed too subversive or too illegal to be shared outside of curators." + desc_with_canvas = "A painting hung away from lesser minds." persistence_id = "library_private" /obj/structure/sign/painting/vv_get_dropdown() @@ -379,11 +428,11 @@ if(!check_rights(NONE)) return var/mob/user = usr - if(!persistence_id || !C) + if(!persistence_id || !current_canvas) to_chat(user,"This is not a persistent painting.") return - var/md5 = md5(C.get_data_string()) - var/author = C.author_ckey + var/md5 = md5(lowertext(current_canvas.get_data_string())) + var/author = current_canvas.author_ckey var/list/current = SSpersistence.paintings[persistence_id] if(current) for(var/list/entry in current) @@ -392,7 +441,8 @@ var/png = "data/paintings/[persistence_id]/[md5].png" fdel(png) for(var/obj/structure/sign/painting/P in SSpersistence.painting_frames) - if(P.C && md5(P.C.get_data_string()) == md5) - QDEL_NULL(P.C) + if(P.current_canvas && md5(P.current_canvas.get_data_string()) == md5) + QDEL_NULL(P.current_canvas) + P.update_icon() log_admin("[key_name(user)] has deleted a persistent painting made by [author].") message_admins("[key_name_admin(user)] has deleted persistent painting made by [author].") diff --git a/code/game/objects/structures/displaycase.dm b/code/game/objects/structures/displaycase.dm index e3b9ae1df1..c32bf81ecf 100644 --- a/code/game/objects/structures/displaycase.dm +++ b/code/game/objects/structures/displaycase.dm @@ -345,7 +345,7 @@ name = initial(I.name) icon = initial(I.icon) icon_state = initial(I.icon_state) -/* Selling people in jars is currently disabled. + /obj/structure/displaycase/forsale name = "vend-a-tray" icon = 'icons/obj/stationobjs.dmi' @@ -354,9 +354,9 @@ density = FALSE max_integrity = 100 req_access = null - showpiece_type = /obj/item/reagent_containers/food + start_showpiece_type = /obj/item/reagent_containers/food alert = FALSE //No, we're not calling the fire department because someone stole your cookie. - glass_fix = FALSE //Fixable with tools instead. + // glass_fix = FALSE //Fixable with tools instead. ///The price of the item being sold. Altered by grab intent ID use. var/sale_price = 20 ///The Account which will receive payment for purchases. Set by the first ID to swipe the tray. @@ -437,7 +437,7 @@ payments_acc.adjust_money(sale_price) usr.put_in_hands(showpiece) to_chat(usr, "You purchase [showpiece] for [sale_price] credits.") - playsound(src, 'sound/effects/cashregister.ogg', 40, TRUE) + // playsound(src, 'sound/effects/cashregister.ogg', 40, TRUE) icon = 'icons/obj/stationobjs.dmi' flick("laserbox_vend", src) showpiece = null @@ -553,4 +553,3 @@ /obj/structure/displaycase/forsale/kitchen desc = "A display case with an ID-card swiper. Use your ID to purchase the contents. Meant for the bartender and chef." req_one_access = list(ACCESS_KITCHEN, ACCESS_BAR) -*/ diff --git a/code/game/objects/structures/femur_breaker.dm b/code/game/objects/structures/femur_breaker.dm index 2ac56ec4fb..cb829006d0 100644 --- a/code/game/objects/structures/femur_breaker.dm +++ b/code/game/objects/structures/femur_breaker.dm @@ -73,9 +73,14 @@ icon_state = "breaker_drop" /obj/structure/femur_breaker/proc/damage_leg(mob/living/carbon/human/H) - H.emote("scream") - H.apply_damage(150, BRUTE, pick(BODY_ZONE_L_LEG, BODY_ZONE_R_LEG)) - H.adjustBruteLoss(rand(5,20) + (max(0, H.health))) //Make absolutely sure they end up in crit, so that they can succumb if they wish. + var/where_we_snappin_boys = pick(BODY_ZONE_L_LEG, BODY_ZONE_R_LEG) + H.emote("scream") + H.apply_damage(150, BRUTE, where_we_snappin_boys) + var/obj/item/bodypart/cracka = H.get_bodypart(where_we_snappin_boys) + if(cracka) + var/datum/wound/blunt/critical/cracka_lackin = new + cracka_lackin.apply_wound(cracka) + H.adjustBruteLoss(rand(5,20) + (max(0, H.health))) //Make absolutely sure they end up in crit, so that they can succumb if they wish. /obj/structure/femur_breaker/proc/raise_slat() slat_status = BREAKER_SLAT_RAISED diff --git a/code/game/objects/structures/lavaland/necropolis_tendril.dm b/code/game/objects/structures/lavaland/necropolis_tendril.dm index edc4f0c91f..67341160de 100644 --- a/code/game/objects/structures/lavaland/necropolis_tendril.dm +++ b/code/game/objects/structures/lavaland/necropolis_tendril.dm @@ -52,12 +52,12 @@ GLOBAL_LIST_INIT(tendrils, list()) last_tendril = FALSE if(last_tendril && !(flags_1 & ADMIN_SPAWNED_1)) - if(SSmedals.hub_enabled) + if(SSachievements.achievements_enabled) for(var/mob/living/L in view(7,src)) if(L.stat || !L.client) continue - SSmedals.UnlockMedal("[BOSS_MEDAL_TENDRIL] [ALL_KILL_MEDAL]", L.client) - SSmedals.SetScore(TENDRIL_CLEAR_SCORE, L.client, 1) + L.client.give_award(/datum/award/achievement/boss/tendril_exterminator, L) + L.client.give_award(/datum/award/score/tendril_score, L) //Progresses score by one GLOB.tendrils -= src QDEL_NULL(emitted_light) QDEL_NULL(gps) diff --git a/code/game/objects/structures/safe.dm b/code/game/objects/structures/safe.dm index e6f77c85a1..55eb100b21 100644 --- a/code/game/objects/structures/safe.dm +++ b/code/game/objects/structures/safe.dm @@ -4,6 +4,11 @@ SAFES FLOOR SAFES */ +/// Chance for a sound clue +#define SOUND_CHANCE 10 +/// Explosion number threshold for opening safe +#define BROKEN_THRESHOLD 3 + //SAFES /obj/structure/safe name = "safe" @@ -14,31 +19,36 @@ FLOOR SAFES density = TRUE resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF interaction_flags_atom = INTERACT_ATOM_ATTACK_HAND | INTERACT_ATOM_UI_INTERACT - var/open = FALSE //is the safe open? - var/tumbler_1_pos //the tumbler position- from 0 to 72 - var/tumbler_1_open //the tumbler position to open at- 0 to 72 - var/tumbler_2_pos - var/tumbler_2_open - var/dial = 0 //where is the dial pointing? - var/space = 0 //the combined w_class of everything in the safe - var/maxspace = 24 //the maximum combined w_class of stuff in the safe - var/explosion_count = 0 //Tough, but breakable - -/obj/structure/safe/New() - ..() - tumbler_1_pos = rand(0, 71) - tumbler_1_open = rand(0, 71) - - tumbler_2_pos = rand(0, 71) - tumbler_2_open = rand(0, 71) - + /// The maximum combined w_class of stuff in the safe + var/maxspace = 24 + /// The amount of tumblers that will be generated + var/number_of_tumblers = 2 + /// Whether the safe is open or not + var/open = FALSE + /// Whether the safe is locked or not + var/locked = TRUE + /// The position the dial is pointing to + var/dial = 0 + /// The list of tumbler dial positions that need to be hit + var/list/tumblers = list() + /// The index in the tumblers list of the tumbler dial position that needs to be hit + var/current_tumbler_index = 1 + /// The combined w_class of everything in the safe + var/space = 0 + /// Tough, but breakable if explosion counts reaches set value + var/explosion_count = 0 /obj/structure/safe/Initialize(mapload) . = ..() + // Combination generation + for(var/i in 1 to number_of_tumblers) + tumblers.Add(rand(0, 99)) + if(!mapload) return + // Put as many items on our turf inside as possible for(var/obj/item/I in loc) if(space >= maxspace) return @@ -46,141 +56,36 @@ FLOOR SAFES space += I.w_class I.forceMove(src) - -/obj/structure/safe/proc/check_unlocked(mob/user, canhear) - if(explosion_count > 2) - return 1 - if(user && canhear) - if(tumbler_1_pos == tumbler_1_open) - to_chat(user, "You hear a [pick("tonk", "krunk", "plunk")] from [src].") - if(tumbler_2_pos == tumbler_2_open) - to_chat(user, "You hear a [pick("tink", "krink", "plink")] from [src].") - if(tumbler_1_pos == tumbler_1_open && tumbler_2_pos == tumbler_2_open) - if(user) - visible_message("[pick("Spring", "Sprang", "Sproing", "Clunk", "Krunk")]!") - return TRUE - return FALSE - -/obj/structure/safe/proc/decrement(num) - num -= 1 - if(num < 0) - num = 71 - return num - -/obj/structure/safe/proc/increment(num) - num += 1 - if(num > 71) - num = 0 - return num - /obj/structure/safe/update_icon_state() if(open) icon_state = "[initial(icon_state)]-open" else icon_state = initial(icon_state) -/obj/structure/safe/ui_interact(mob/user) - user.set_machine(src) - var/dat = "
" - dat += "[open ? "Close" : "Open"] [src] | - [dial] +" - if(open) - dat += "" - for(var/i = contents.len, i>=1, i--) - var/obj/item/P = contents[i] - dat += "" - dat += "
[P.name]
" - user << browse("[name][dat]", "window=safe;size=350x300") - -/obj/structure/safe/Topic(href, href_list) - if(!ishuman(usr)) - return - var/mob/living/carbon/human/user = usr - - if(!user.canUseTopic(src)) - return - - var/canhear = FALSE - if(user.is_holding_item_of_type(/obj/item/clothing/neck/stethoscope)) - canhear = TRUE - - if(href_list["open"]) - if(check_unlocked()) - to_chat(user, "You [open ? "close" : "open"] [src].") - open = !open - update_icon() - updateUsrDialog() - return - else - to_chat(user, "You can't [open ? "close" : "open"] [src], the lock is engaged!") - return - - if(href_list["decrement"]) - dial = decrement(dial) - if(dial == tumbler_1_pos + 1 || dial == tumbler_1_pos - 71) - tumbler_1_pos = decrement(tumbler_1_pos) - if(canhear) - to_chat(user, "You hear a [pick("clack", "scrape", "clank")] from [src].") - if(tumbler_1_pos == tumbler_2_pos + 37 || tumbler_1_pos == tumbler_2_pos - 35) - tumbler_2_pos = decrement(tumbler_2_pos) - if(canhear) - to_chat(user, "You hear a [pick("click", "chink", "clink")] from [src].") - check_unlocked(user, canhear) - updateUsrDialog() - return - - if(href_list["increment"]) - dial = increment(dial) - if(dial == tumbler_1_pos - 1 || dial == tumbler_1_pos + 71) - tumbler_1_pos = increment(tumbler_1_pos) - if(canhear) - to_chat(user, "You hear a [pick("clack", "scrape", "clank")] from [src].") - if(tumbler_1_pos == tumbler_2_pos - 37 || tumbler_1_pos == tumbler_2_pos + 35) - tumbler_2_pos = increment(tumbler_2_pos) - if(canhear) - to_chat(user, "You hear a [pick("click", "chink", "clink")] from [src].") - check_unlocked(user, canhear) - updateUsrDialog() - return - - if(href_list["retrieve"]) - user << browse("", "window=safe") // Close the menu - - var/obj/item/P = locate(href_list["retrieve"]) in src - if(open) - if(P && in_range(src, user)) - user.put_in_hands(P) - space -= P.w_class - updateUsrDialog() - - /obj/structure/safe/attackby(obj/item/I, mob/user, params) if(open) - . = 1 //no afterattack + . = TRUE //no afterattack if(I.w_class + space <= maxspace) space += I.w_class if(!user.transferItemToLoc(I, src)) to_chat(user, "\The [I] is stuck to your hand, you cannot put it in the safe!") return to_chat(user, "You put [I] in [src].") - updateUsrDialog() + else + to_chat(user, "[I] won't fit in [src].") + else + if(istype(I, /obj/item/clothing/neck/stethoscope)) + attack_hand(user) return else - to_chat(user, "[I] won't fit in [src].") + to_chat(user, "You can't put [I] into the safe while it is closed!") return - else if(istype(I, /obj/item/clothing/neck/stethoscope)) - to_chat(user, "Hold [I] in one of your hands while you manipulate the dial!") - else - return ..() - - -/obj/structure/safe/handle_atom_del(atom/A) - updateUsrDialog() /obj/structure/safe/blob_act(obj/structure/blob/B) return /obj/structure/safe/ex_act(severity, target) - if(((severity == 2 && target == src) || severity == 1) && explosion_count < 3) + if(((severity == 2 && target == src) || severity == 1) && explosion_count < BROKEN_THRESHOLD) explosion_count++ switch(explosion_count) if(1) @@ -190,6 +95,144 @@ FLOOR SAFES if(3) desc = initial(desc) + "\nThe lock seems to be broken." +/obj/structure/safe/ui_assets(mob/user) + return list( + get_asset_datum(/datum/asset/simple/safe), + ) + +/obj/structure/safe/ui_state(mob/user) + return GLOB.physical_state + +/obj/structure/safe/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "Safe", name) + ui.open() + +/obj/structure/safe/ui_data(mob/user) + var/list/data = list() + data["dial"] = dial + data["open"] = open + data["locked"] = locked + data["broken"] = check_broken() + + if(open) + var/list/contents_names = list() + data["contents"] = contents_names + for(var/obj/O in contents) + contents_names[++contents_names.len] = list("name" = O.name, "sprite" = O.icon_state) + user << browse_rsc(icon(O.icon, O.icon_state), "[O.icon_state].png") + + return data + +/obj/structure/safe/ui_act(action, params) + . = ..() + if(.) + return + + if(!ishuman(usr)) + return + var/mob/living/carbon/human/user = usr + if(!user.canUseTopic(src, BE_CLOSE)) + return + + var/canhear = FALSE + if(user.is_holding_item_of_type(/obj/item/clothing/neck/stethoscope)) + canhear = TRUE + + switch(action) + if("open") + if(!check_unlocked() && !open && !broken) + to_chat(user, "You cannot open [src], as its lock is engaged!") + return + to_chat(user, "You [open ? "close" : "open"] [src].") + open = !open + update_icon() + return TRUE + if("turnright") + if(open) + return + if(broken) + to_chat(user, "The dial will not turn, as the mechanism is destroyed!") + return + var/ticks = text2num(params["num"]) + for(var/i = 1 to ticks) + dial = WRAP(dial - 1, 0, 100) + + var/invalid_turn = current_tumbler_index % 2 == 0 || current_tumbler_index > number_of_tumblers + if(invalid_turn) // The moment you turn the wrong way or go too far, the tumblers reset + current_tumbler_index = 1 + + if(!invalid_turn && dial == tumblers[current_tumbler_index]) + notify_user(user, canhear, list("tink", "krink", "plink"), ticks, i) + current_tumbler_index++ + else + notify_user(user, canhear, list("clack", "scrape", "clank"), ticks, i) + check_unlocked() + return TRUE + if("turnleft") + if(open) + return + if(broken) + to_chat(user, "The dial will not turn, as the mechanism is destroyed!") + return + var/ticks = text2num(params["num"]) + for(var/i = 1 to ticks) + dial = WRAP(dial + 1, 0, 100) + + var/invalid_turn = current_tumbler_index % 2 != 0 || current_tumbler_index > number_of_tumblers + if(invalid_turn) // The moment you turn the wrong way or go too far, the tumblers reset + current_tumbler_index = 1 + + if(!invalid_turn && dial == tumblers[current_tumbler_index]) + notify_user(user, canhear, list("tonk", "krunk", "plunk"), ticks, i) + current_tumbler_index++ + else + notify_user(user, canhear, list("click", "chink", "clink"), ticks, i) + check_unlocked() + return TRUE + if("retrieve") + if(!open) + return + var/index = text2num(params["index"]) + if(!index) + return + var/obj/item/I = contents[index] + if(!I || !in_range(src, user)) + return + user.put_in_hands(I) + space -= I.w_class + return TRUE + +/** + * Checks if safe is considered in a broken state for force-opening the safe + */ +/obj/structure/safe/proc/check_broken() + return broken || explosion_count >= BROKEN_THRESHOLD + +/** + * Called every dial turn to determine whether the safe should unlock or not. + */ +/obj/structure/safe/proc/check_unlocked() + if(check_broken()) + return TRUE + if(current_tumbler_index > number_of_tumblers) + locked = FALSE + visible_message("[pick("Spring", "Sprang", "Sproing", "Clunk", "Krunk")]!") + return TRUE + locked = TRUE + return FALSE + +/** + * Called every dial turn to provide feedback if possible. + */ +/obj/structure/safe/proc/notify_user(user, canhear, sounds, total_ticks, current_tick) + if(!canhear) + return + if(current_tick == 2) + to_chat(user, "The sounds from [src] are too fast and blend together.") + if(total_ticks == 1 || prob(SOUND_CHANCE)) + to_chat(user, "You hear a [pick(sounds)] from [src].") //FLOOR SAFES /obj/structure/safe/floor @@ -199,13 +242,11 @@ FLOOR SAFES level = 1 //underfloor layer = LOW_OBJ_LAYER - /obj/structure/safe/floor/Initialize(mapload) . = ..() if(mapload) var/turf/T = loc hide(T.intact) - -/obj/structure/safe/floor/hide(var/intact) - invisibility = intact ? INVISIBILITY_MAXIMUM : 0 +#undef SOUND_CHANCE +#undef BROKEN_THRESHOLD diff --git a/code/game/objects/structures/watercloset.dm b/code/game/objects/structures/watercloset.dm index 2d3ec4bc1f..dba8d5de45 100644 --- a/code/game/objects/structures/watercloset.dm +++ b/code/game/objects/structures/watercloset.dm @@ -47,14 +47,19 @@ if(open) GM.visible_message("[user] starts to give [GM] a swirlie!", "[user] starts to give you a swirlie...") swirlie = GM - if(do_after(user, 30, 0, target = src)) - GM.visible_message("[user] gives [GM] a swirlie!", "[user] gives you a swirlie!", "You hear a toilet flushing.") + var/was_alive = (swirlie.stat != DEAD) + if(do_after(user, 3 SECONDS, target = src)) + GM.visible_message("[user] gives [GM] a swirlie!", "[user] gives you a swirlie!", "You hear a toilet flushing.") if(iscarbon(GM)) var/mob/living/carbon/C = GM if(!C.internal) + log_combat(user, C, "swirlied (oxy)") C.adjustOxyLoss(5) else + log_combat(user, GM, "swirlied (oxy)") GM.adjustOxyLoss(5) + if(was_alive && swirlie.stat == DEAD && swirlie.client) + swirlie.client.give_award(/datum/award/achievement/misc/swirlie, swirlie) // just like space high school all over again! swirlie = null else playsound(src.loc, 'sound/effects/bang.ogg', 25, 1) diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm index d0b21016d4..53bcd28fa1 100644 --- a/code/game/objects/structures/window.dm +++ b/code/game/objects/structures/window.dm @@ -584,6 +584,9 @@ GLOBAL_LIST_EMPTY(electrochromatic_window_lookup) /obj/structure/window/plasma/reinforced/unanchored anchored = FALSE +/obj/structure/window/plasma/reinforced/BlockSuperconductivity() + return TRUE + /obj/structure/window/reinforced/tinted name = "tinted window" icon_state = "twindow" diff --git a/code/game/turfs/simulated/minerals.dm b/code/game/turfs/simulated/minerals.dm index 0f1ec6fa85..e75c7dde55 100644 --- a/code/game/turfs/simulated/minerals.dm +++ b/code/game/turfs/simulated/minerals.dm @@ -872,20 +872,18 @@ /turf/closed/mineral/strong/gets_drilled(mob/user) if(!ishuman(user)) return // see attackby - /* var/mob/living/carbon/human/H = user - if(!(H.mind.get_skill_level(/datum/skill/mining) >= SKILL_LEVEL_MASTER)) - return - */ + // if(!(H.mind?.get_skill_level(/datum/skill/mining) >= SKILL_LEVEL_MASTER)) + // return drop_ores() -// H.client.give_award(/datum/award/achievement/skill/legendary_miner, H) + H.client.give_award(/datum/award/achievement/skill/legendary_miner, H) var/flags = NONE if(defer_change) // TODO: make the defer change var a var for any changeturf flag flags = CHANGETURF_DEFER_CHANGE ScrapeAway(flags=flags) addtimer(CALLBACK(src, .proc/AfterChange), 1, TIMER_UNIQUE) playsound(src, 'sound/effects/break_stone.ogg', 50, TRUE) //beautiful destruction -// H.mind.adjust_experience(/datum/skill/mining, 100) //yay! + // H.mind?.adjust_experience(/datum/skill/mining, 100) //yay! /turf/closed/mineral/strong/proc/drop_ores() if(prob(10)) diff --git a/code/modules/admin/DB_ban/functions.dm b/code/modules/admin/DB_ban/functions.dm index 39c4d2d939..4c00e8f010 100644 --- a/code/modules/admin/DB_ban/functions.dm +++ b/code/modules/admin/DB_ban/functions.dm @@ -80,8 +80,11 @@ var/client/banned_client = banned_mob?.client var/banned_mob_guest_key = had_banned_mob && IsGuestKey(banned_mob.key) banned_mob = null - var/sql_ckey = sanitizeSQL(ckey) - var/datum/DBQuery/query_add_ban_get_ckey = SSdbcore.NewQuery("SELECT 1 FROM [format_table_name("player")] WHERE ckey = '[sql_ckey]'") + var/datum/db_query/query_add_ban_get_ckey = SSdbcore.NewQuery({" + SELECT 1 + FROM [format_table_name("player")] + WHERE ckey = :ckey"}, + list("ckey" = ckey)) if(!query_add_ban_get_ckey.warn_execute()) qdel(query_add_ban_get_ckey) return @@ -122,10 +125,11 @@ else adminwho += ", [C]" - reason = sanitizeSQL(reason) - var/sql_a_ckey = sanitizeSQL(a_ckey) if(maxadminbancheck) - var/datum/DBQuery/query_check_adminban_amt = SSdbcore.NewQuery("SELECT count(id) AS num FROM [format_table_name("ban")] WHERE (a_ckey = '[sql_a_ckey]') AND (bantype = 'ADMIN_PERMABAN' OR (bantype = 'ADMIN_TEMPBAN' AND expiration_time > Now())) AND isnull(unbanned)") + var/datum/db_query/query_check_adminban_amt = SSdbcore.NewQuery({" + SELECT count(id) AS num FROM [format_table_name("ban")] + WHERE (a_ckey = :a_ckey) AND (bantype = 'ADMIN_PERMABAN' OR (bantype = 'ADMIN_TEMPBAN' AND expiration_time > Now())) AND isnull(unbanned) + "}, list("a_ckey" = a_ckey)) if(!query_check_adminban_amt.warn_execute()) qdel(query_check_adminban_amt) return @@ -143,13 +147,15 @@ computerid = "0" if(!ip) ip = "0.0.0.0" - var/sql_job = sanitizeSQL(job) - var/sql_computerid = sanitizeSQL(computerid) - var/sql_ip = sanitizeSQL(ip) - var/sql_a_computerid = sanitizeSQL(a_computerid) - var/sql_a_ip = sanitizeSQL(a_ip) - var/sql = "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(IF('[world.internet_address]' LIKE '', '0', '[world.internet_address]')), '[world.port]', '[GLOB.round_id]', '[bantype_str]', '[reason]', '[sql_job]', [(duration)?"[duration]":"0"], Now() + INTERVAL [(duration>0) ? duration : 0] MINUTE, '[sql_ckey]', '[sql_computerid]', INET_ATON('[sql_ip]'), '[sql_a_ckey]', '[sql_a_computerid]', INET_ATON('[sql_a_ip]'), '[who]', '[adminwho]')" - var/datum/DBQuery/query_add_ban = SSdbcore.NewQuery(sql) + 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(:internet_address), :port, :round_id, :bantype_str, :reason, :job, :duration, Now() + INTERVAL :expiration_time MINUTE, :ckey, :computerid, INET_ATON(:ip), :a_ckey, :a_computerid, INET_ATON(:a_ip), :who, :adminwho) + "}, list("internet_address" = world.internet_address ? world.internet_address : 0, + "port" = world.port, "round_id" = GLOB.round_id, "bantype_str" = bantype_str, "reason" = reason, + "job" = job, "duration" = duration ? "[duration]":"0", "expiration_time" = (duration > 0) ? duration : 0, + "ckey" = ckey, "computerid" = computerid, "ip" = ip, + "a_ckey" = a_ckey, "a_computerid" = a_computerid, "a_ip" = a_ip, "who" = who, "adminwho" = adminwho + )) if(!query_add_ban.warn_execute()) qdel(query_add_ban) return @@ -160,7 +166,7 @@ var/datum/admin_help/AH = admin_ticket_log(ckey, msg) if(announceinirc) - send2irc("BAN ALERT","[a_key] applied a [bantype_str] on [bankey]") + send2adminchat("BAN ALERT","[a_key] applied a [bantype_str] on [bankey]") if(kickbannedckey) if(AH) @@ -212,11 +218,11 @@ bantype_sql = "(bantype = 'JOB_PERMABAN' OR (bantype = 'JOB_TEMPBAN' AND expiration_time > Now() ) )" else bantype_sql = "bantype = '[bantype_str]'" - var/sql_ckey = sanitizeSQL(ckey) - var/sql = "SELECT id FROM [format_table_name("ban")] WHERE ckey = '[sql_ckey]' AND [bantype_sql] AND (unbanned is null OR unbanned = false)" + var/sql = "SELECT id FROM [format_table_name("ban")] WHERE ckey = :ckey AND [bantype_sql] AND (unbanned is null OR unbanned = false)" + var/list/sql_args = list("ckey" = ckey) if(job) - var/sql_job = sanitizeSQL(job) - sql += " AND job = '[sql_job]'" + sql += " AND job = :job" + sql_args["job"] = job if(!SSdbcore.Connect()) return @@ -224,7 +230,7 @@ var/ban_id var/ban_number = 0 //failsafe - var/datum/DBQuery/query_unban_get_id = SSdbcore.NewQuery(sql) + var/datum/db_query/query_unban_get_id = SSdbcore.NewQuery(sql, sql_args) if(!query_unban_get_id.warn_execute()) qdel(query_unban_get_id) return @@ -258,7 +264,7 @@ to_chat(usr, "Cancelled") return - var/datum/DBQuery/query_edit_ban_get_details = SSdbcore.NewQuery("SELECT IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("ban")].ckey), ckey), duration, reason FROM [format_table_name("ban")] WHERE id = [banid]") + var/datum/db_query/query_edit_ban_get_details = SSdbcore.NewQuery("SELECT IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("ban")].ckey), ckey), duration, reason FROM [format_table_name("ban")] WHERE id = [banid]") if(!query_edit_ban_get_details.warn_execute()) qdel(query_edit_ban_get_details) return @@ -278,19 +284,17 @@ return qdel(query_edit_ban_get_details) - reason = sanitizeSQL(reason) var/value switch(param) if("reason") if(!value) value = input("Insert the new reason for [p_key]'s ban", "New Reason", "[reason]", null) as null|text - value = sanitizeSQL(value) if(!value) to_chat(usr, "Cancelled") return - var/datum/DBQuery/query_edit_ban_reason = SSdbcore.NewQuery("UPDATE [format_table_name("ban")] SET reason = '[value]', edits = CONCAT(edits,'- [e_key] changed ban reason from \\\"[reason]\\\" to \\\"[value]\\\"
') WHERE id = [banid]") + var/datum/db_query/query_edit_ban_reason = SSdbcore.NewQuery("UPDATE [format_table_name("ban")] SET reason = '[value]', edits = CONCAT(edits,'- [e_key] changed ban reason from \\\"[reason]\\\" to \\\"[value]\\\"
') WHERE id = [banid]") if(!query_edit_ban_reason.warn_execute()) qdel(query_edit_ban_reason) return @@ -303,7 +307,7 @@ to_chat(usr, "Cancelled") return - var/datum/DBQuery/query_edit_ban_duration = SSdbcore.NewQuery("UPDATE [format_table_name("ban")] SET duration = [value], edits = CONCAT(edits,'- [e_key] changed ban duration from [duration] to [value]
'), expiration_time = DATE_ADD(bantime, INTERVAL [value] MINUTE) WHERE id = [banid]") + var/datum/db_query/query_edit_ban_duration = SSdbcore.NewQuery("UPDATE [format_table_name("ban")] SET duration = [value], edits = CONCAT(edits,'- [e_key] changed ban duration from [duration] to [value]
'), expiration_time = DATE_ADD(bantime, INTERVAL [value] MINUTE) WHERE id = [banid]") if(!query_edit_ban_duration.warn_execute()) qdel(query_edit_ban_duration) return @@ -333,7 +337,7 @@ var/ban_number = 0 //failsafe var/p_key - var/datum/DBQuery/query_unban_get_ckey = SSdbcore.NewQuery(sql) + var/datum/db_query/query_unban_get_ckey = SSdbcore.NewQuery(sql) if(!query_unban_get_ckey.warn_execute()) qdel(query_unban_get_ckey) return @@ -358,7 +362,7 @@ var/unban_ip = owner.address var/sql_update = "UPDATE [format_table_name("ban")] SET unbanned = 1, unbanned_datetime = Now(), unbanned_ckey = '[unban_ckey]', unbanned_computerid = '[unban_computerid]', unbanned_ip = INET_ATON('[unban_ip]') WHERE id = [id]" - var/datum/DBQuery/query_unban = SSdbcore.NewQuery(sql_update) + var/datum/db_query/query_unban = SSdbcore.NewQuery(sql_update) if(!query_unban.warn_execute()) qdel(query_unban) return @@ -448,20 +452,28 @@ if(adminckey || playerckey || ip || cid) var/list/searchlist = list() + var/list/searchlist_args = list() if(playerckey) - searchlist += "ckey = '[sanitizeSQL(ckey(playerckey))]'" + searchlist += "ckey = :playerckey" + searchlist_args["playerckey"] = playerckey if(adminckey) - searchlist += "a_ckey = '[sanitizeSQL(ckey(adminckey))]'" + searchlist += "a_ckey = :adminckey" + searchlist_args["adminckey"] = adminckey if(ip) - searchlist += "ip = INET_ATON('[sanitizeSQL(ip)]')" + searchlist += "ip = INET_ATON(:ip)" + searchlist_args["ip"] = ip if(cid) - searchlist += "computerid = '[sanitizeSQL(cid)]'" - var/search = searchlist.Join(" AND ") + searchlist += "computerid = :cid" + searchlist_args["cid"] = cid + var/search = searchlist.Join(" AND ") // x = x AND y = z var/bancount = 0 var/bansperpage = 15 var/pagecount = 0 page = text2num(page) - var/datum/DBQuery/query_count_bans = SSdbcore.NewQuery("SELECT COUNT(id) FROM [format_table_name("ban")] WHERE [search]") + var/datum/db_query/query_count_bans = SSdbcore.NewQuery({" + SELECT COUNT(id) FROM [format_table_name("ban")] + WHERE [search] + "}, searchlist_args) if(!query_count_bans.warn_execute()) qdel(query_count_bans) return @@ -489,7 +501,11 @@ output += "OPTIONS" output += "" var/limit = " LIMIT [bansperpage * page], [bansperpage]" - var/datum/DBQuery/query_search_bans = SSdbcore.NewQuery("SELECT id, bantime, bantype, reason, job, duration, expiration_time, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("ban")].ckey), ckey), IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("ban")].a_ckey), a_ckey), unbanned, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("ban")].unbanned_ckey), unbanned_ckey), unbanned_datetime, edits, round_id FROM [format_table_name("ban")] WHERE [search] ORDER BY bantime DESC[limit]") + + var/datum/db_query/query_search_bans = SSdbcore.NewQuery({" + SELECT id, bantime, bantype, reason, job, duration, expiration_time, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("ban")].ckey), ckey), IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("ban")].a_ckey), a_ckey), unbanned, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("ban")].unbanned_ckey), unbanned_ckey), unbanned_datetime, edits, round_id + FROM [format_table_name("ban")] + WHERE [search] ORDER BY bantime DESC[limit]"}, searchlist_args) if(!query_search_bans.warn_execute()) qdel(query_search_bans) return diff --git a/code/modules/admin/IsBanned.dm b/code/modules/admin/IsBanned.dm index 63facade2e..825e2d83c7 100644 --- a/code/modules/admin/IsBanned.dm +++ b/code/modules/admin/IsBanned.dm @@ -100,7 +100,18 @@ if(computer_id) cidquery = " OR computerid = '[computer_id]' " - var/datum/DBQuery/query_ban_check = SSdbcore.NewQuery("SELECT IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("ban")].ckey), ckey), IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("ban")].a_ckey), a_ckey), reason, expiration_time, duration, bantime, bantype, id, round_id FROM [format_table_name("ban")] WHERE (ckey = '[ckey]' [ipquery] [cidquery]) AND (bantype = 'PERMABAN' OR bantype = 'ADMIN_PERMABAN' OR ((bantype = 'TEMPBAN' OR bantype = 'ADMIN_TEMPBAN') AND expiration_time > Now())) AND isnull(unbanned)") + var/datum/db_query/query_ban_check = SSdbcore.NewQuery({" + SELECT IFNULL((SELECT byond_key + FROM [format_table_name("player")] + WHERE [format_table_name("player")].ckey = [format_table_name("ban")].ckey), ckey), + IFNULL((SELECT byond_key FROM [format_table_name("player")] + WHERE [format_table_name("player")].ckey = [format_table_name("ban")].a_ckey), a_ckey), reason, expiration_time, duration, bantime, bantype, id, round_id FROM [format_table_name("ban")] + WHERE (ckey = :ckey [ipquery] [cidquery]) + AND (bantype = 'PERMABAN' OR bantype = 'ADMIN_PERMABAN' OR ((bantype = 'TEMPBAN' OR bantype = 'ADMIN_TEMPBAN') + AND expiration_time > Now())) AND isnull(unbanned) + "}, list( + "ckey" = ckey + )) if(!query_ban_check.Execute(async = TRUE)) qdel(query_ban_check) key_cache[key] = 0 diff --git a/code/modules/admin/admin_ranks.dm b/code/modules/admin/admin_ranks.dm index 39053b8e15..79ba4abae7 100644 --- a/code/modules/admin/admin_ranks.dm +++ b/code/modules/admin/admin_ranks.dm @@ -119,16 +119,12 @@ GLOBAL_PROTECT(protected_ranks) set waitfor = FALSE if(IsAdminAdvancedProcCall()) - to_chat(usr, "Admin rank DB Sync blocked: Advanced ProcCall detected.") + to_chat(usr, "Admin rank DB Sync blocked: Advanced ProcCall detected.", confidential = TRUE) return var/list/sql_ranks = list() for(var/datum/admin_rank/R in GLOB.protected_ranks) - var/sql_rank = sanitizeSQL(R.name) - var/sql_flags = sanitizeSQL(R.include_rights) - var/sql_exclude_flags = sanitizeSQL(R.exclude_rights) - var/sql_can_edit_flags = sanitizeSQL(R.can_edit_rights) - sql_ranks += list(list("rank" = "'[sql_rank]'", "flags" = "[sql_flags]", "exclude_flags" = "[sql_exclude_flags]", "can_edit_flags" = "[sql_can_edit_flags]")) + sql_ranks += list(list("rank" = R.name, "flags" = R.include_rights, "exclude_flags" = R.exclude_rights, "can_edit_flags" = R.can_edit_rights)) SSdbcore.MassInsert(format_table_name("admin_ranks"), sql_ranks, duplicate_key = TRUE) //load our rank - > rights associations @@ -160,7 +156,7 @@ GLOBAL_PROTECT(protected_ranks) if(!no_update) sync_ranks_with_db() else - var/datum/DBQuery/query_load_admin_ranks = SSdbcore.NewQuery("SELECT rank, flags, exclude_flags, can_edit_flags FROM [format_table_name("admin_ranks")]") + var/datum/db_query/query_load_admin_ranks = SSdbcore.NewQuery("SELECT `rank`, flags, exclude_flags, can_edit_flags FROM [format_table_name("admin_ranks")]") if(!query_load_admin_ranks.Execute()) message_admins("Error loading admin ranks from database. Loading from backup.") log_sql("Error loading admin ranks from database. Loading from backup.") @@ -168,7 +164,7 @@ GLOBAL_PROTECT(protected_ranks) else while(query_load_admin_ranks.NextRow()) var/skip - var/rank_name = ckeyEx(query_load_admin_ranks.item[1]) + var/rank_name = query_load_admin_ranks.item[1] for(var/datum/admin_rank/R in GLOB.admin_ranks) if(R.name == rank_name) //this rank was already loaded from txt override skip = 1 @@ -234,20 +230,12 @@ GLOBAL_PROTECT(protected_ranks) for(var/datum/admin_rank/R in GLOB.admin_ranks) rank_names[R.name] = R //ckeys listed in admins.txt are always made admins before sql loading is attempted - var/list/lines = world.file2list("[global.config.directory]/admins.txt") - for(var/line in lines) - if(!length(line) || findtextEx(line, "#", 1, 2)) - continue - var/list/entry = splittext(line, "=") - if(entry.len < 2) - continue - var/ckey = ckey(entry[1]) - var/rank = ckeyEx(entry[2]) - if(!ckey || !rank) - continue - new /datum/admins(rank_names[rank], ckey, 0, 1) + var/admins_text = file2text("[global.config.directory]/admins.txt") + var/regex/admins_regex = new(@"^(?!#)(.+?)\s+=\s+(.+)", "gm") + while(admins_regex.Find(admins_text)) + new /datum/admins(rank_names[admins_regex.group[2]], ckey(admins_regex.group[1]), FALSE, TRUE) if(!CONFIG_GET(flag/admin_legacy_system) || dbfail) - var/datum/DBQuery/query_load_admins = SSdbcore.NewQuery("SELECT ckey, rank FROM [format_table_name("admin")] ORDER BY rank") + var/datum/db_query/query_load_admins = SSdbcore.NewQuery("SELECT ckey, `rank` FROM [format_table_name("admin")] ORDER BY `rank`") if(!query_load_admins.Execute()) message_admins("Error loading admins from database. Loading from backup.") log_sql("Error loading admins from database. Loading from backup.") @@ -255,7 +243,7 @@ GLOBAL_PROTECT(protected_ranks) else while(query_load_admins.NextRow()) var/admin_ckey = ckey(query_load_admins.item[1]) - var/admin_rank = ckeyEx(query_load_admins.item[2]) + var/admin_rank = query_load_admins.item[2] var/skip if(rank_names[admin_rank] == null) message_admins("[admin_ckey] loaded with invalid admin rank [admin_rank].") diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index e2c12353f7..f22ceb6a2d 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -6,13 +6,21 @@ GLOBAL_PROTECT(admin_verbs_default) return list( /client/proc/deadmin, /*destroys our own admin datum so we can play as a regular player*/ /client/proc/cmd_admin_say, /*admin-only ooc chat*/ - /client/proc/dsay, /*talk in deadchat using our ckey/fakekey*/ - /client/proc/deadchat, - /client/proc/investigate_show, /*various admintools for investigation. Such as a singulo grief-log*/ + /client/proc/hide_verbs, /*hides all our adminverbs*/ + /client/proc/hide_most_verbs, /*hides all our hideable adminverbs*/ /client/proc/debug_variables, /*allows us to -see- the variables of any instance in the game. +VAREDIT needed to modify*/ - /client/proc/toggleprayers, - /client/proc/toggleadminhelpsound, - /client/proc/debugstatpanel, + /client/proc/dsay, /*talk in deadchat using our ckey/fakekey*/ + /client/proc/investigate_show, /*various admintools for investigation. Such as a singulo grief-log*/ + /client/proc/secrets, + /client/proc/toggle_hear_radio, /*allows admins to hide all radio output*/ + /client/proc/reload_admins, + /client/proc/reestablish_db_connection, /*reattempt a connection to the database*/ + /client/proc/cmd_admin_pm_context, /*right-click adminPM interface*/ + /client/proc/cmd_admin_pm_panel, /*admin-pm list*/ + /client/proc/stop_sounds, + /client/proc/mark_datum_mapview, + /client/proc/debugstatpanel + // /client/proc/fix_air /*resets air in designated radius to its default atmos composition*/ ) GLOBAL_LIST_INIT(admin_verbs_admin, world.AVerbsAdmin()) GLOBAL_PROTECT(admin_verbs_admin) @@ -24,6 +32,7 @@ GLOBAL_PROTECT(admin_verbs_admin) /datum/verbs/menu/Admin/verb/playerpanel, /client/proc/game_panel, /*game panel, allows to change game-mode etc*/ /client/proc/check_ai_laws, /*shows AI and borg laws*/ + // /client/proc/ghost_pool_protection, /*opens a menu for toggling ghost roles*/ /datum/admins/proc/toggleooc, /*toggles ooc on/off for everyone*/ /datum/admins/proc/toggleooclocal, /*toggles looc on/off for everyone*/ /datum/admins/proc/toggleoocdead, /*toggles ooc on/off for everyone who is dead*/ @@ -35,15 +44,15 @@ GLOBAL_PROTECT(admin_verbs_admin) /client/proc/admin_ghost, /*allows us to ghost/reenter body at will*/ /client/proc/toggle_view_range, /*changes how far we can see*/ /client/proc/getserverlogs, /*for accessing server logs*/ - /client/proc/cmd_admin_subtle_message, /*send an message to somebody as a 'voice in their head'*/ - /client/proc/cmd_admin_headset_message, /*send an message to somebody through their headset as CentCom*/ + /client/proc/getcurrentlogs, /*for accessing server logs for the current round*/ + /client/proc/cmd_admin_subtle_message, /*send a message to somebody as a 'voice in their head'*/ + /client/proc/cmd_admin_headset_message, /*send a message to somebody through their headset as CentCom*/ /client/proc/cmd_admin_delete, /*delete an instance/object/mob/etc*/ /client/proc/cmd_admin_check_contents, /*displays the contents of an instance*/ /client/proc/centcom_podlauncher,/*Open a window to launch a Supplypod and configure it or it's contents*/ /client/proc/check_antagonists, /*shows all antags*/ /datum/admins/proc/access_news_network, /*allows access of newscasters*/ /client/proc/jumptocoord, /*we ghost and jump to a coordinate*/ - /client/proc/getcurrentlogs, /*for accessing server logs for the current round*/ /client/proc/Getmob, /*teleports a mob to our location*/ /client/proc/Getkey, /*teleports a mob with a certain ckey to our location*/ // /client/proc/sendmob, /*sends a mob somewhere*/ -Removed due to it needing two sorting procs to work, which were executed every time an admin right-clicked. ~Errorage @@ -53,6 +62,8 @@ GLOBAL_PROTECT(admin_verbs_admin) /client/proc/jumptoturf, /*allows us to jump to a specific turf*/ /client/proc/admin_call_shuttle, /*allows us to call the emergency shuttle*/ /client/proc/admin_cancel_shuttle, /*allows us to cancel the emergency shuttle, sending it back to centcom*/ + // /client/proc/admin_disable_shuttle, /*allows us to disable the emergency shuttle admin-wise so that it cannot be called*/ + // /client/proc/admin_enable_shuttle, /*undoes the above*/ /client/proc/cmd_admin_direct_narrate, /*send text directly to a player with no padding. Useful for narratives and fluff-text*/ /client/proc/cmd_admin_world_narrate, /*sends text to all players with no padding*/ /client/proc/cmd_admin_local_narrate, /*sends text to all mobs within view of atom*/ @@ -64,25 +75,19 @@ GLOBAL_PROTECT(admin_verbs_admin) /client/proc/toggle_combo_hud, // toggle display of the combination pizza antag and taco sci/med/eng hud /client/proc/toggle_AI_interact, /*toggle admin ability to interact with machines as an AI*/ /datum/admins/proc/open_shuttlepanel, /* Opens shuttle manipulator UI */ + /client/proc/deadchat, + /client/proc/toggleprayers, + // /client/proc/toggle_prayer_sound, + // /client/proc/colorasay, + // /client/proc/resetasaycolor, + /client/proc/toggleadminhelpsound, /client/proc/respawn_character, - /client/proc/secrets, - /client/proc/toggle_hear_radio, /*allows admins to hide all radio output*/ - /client/proc/reload_admins, - /client/proc/reestablish_db_connection, /*reattempt a connection to the database*/ - /client/proc/cmd_admin_pm_context, /*right-click adminPM interface*/ - /client/proc/cmd_admin_pm_panel, /*admin-pm list*/ - /client/proc/panicbunker, - /client/proc/addbunkerbypass, - /client/proc/revokebunkerbypass, - /client/proc/stop_sounds, - /client/proc/mark_datum_mapview, - /client/proc/hide_verbs, /*hides all our adminverbs*/ - /client/proc/hide_most_verbs, /*hides all our hideable adminverbs*/ - /datum/admins/proc/open_borgopanel, - /client/proc/admin_cmd_respawn_return_to_lobby, - /client/proc/admin_cmd_remove_ghost_respawn_timer + /client/proc/admin_cmd_respawn_return_to_lobby, //CIT + /client/proc/admin_cmd_remove_ghost_respawn_timer, //CIT + /client/proc/addbunkerbypass, //CIT + /client/proc/revokebunkerbypass, //CIT + /datum/admins/proc/open_borgopanel ) - GLOBAL_LIST_INIT(admin_verbs_ban, list(/client/proc/unban_panel, /client/proc/DB_ban_panel, /client/proc/stickybanpanel)) GLOBAL_PROTECT(admin_verbs_ban) GLOBAL_LIST_INIT(admin_verbs_sounds, list(/client/proc/play_local_sound, /client/proc/play_sound, /client/proc/manual_play_web_sound, /client/proc/set_round_end_sound)) @@ -110,13 +115,14 @@ GLOBAL_LIST_INIT(admin_verbs_fun, list( /client/proc/show_tip, /client/proc/smite, /client/proc/admin_away, - /client/proc/cmd_admin_toggle_fov, + /client/proc/cmd_admin_toggle_fov, //CIT CHANGE - FOV /client/proc/roll_dices //CIT CHANGE - Adds dice verb )) GLOBAL_PROTECT(admin_verbs_fun) GLOBAL_LIST_INIT(admin_verbs_spawn, list(/datum/admins/proc/spawn_atom, /datum/admins/proc/podspawn_atom, /datum/admins/proc/spawn_cargo, /datum/admins/proc/spawn_objasmob, /client/proc/respawn_character)) GLOBAL_PROTECT(admin_verbs_spawn) GLOBAL_LIST_INIT(admin_verbs_server, world.AVerbsServer()) +GLOBAL_PROTECT(admin_verbs_server) /world/proc/AVerbsServer() return list( /datum/admins/proc/startnow, @@ -126,17 +132,20 @@ GLOBAL_LIST_INIT(admin_verbs_server, world.AVerbsServer()) /datum/admins/proc/toggleaban, /client/proc/everyone_random, /datum/admins/proc/toggleAI, - /datum/admins/proc/toggleMulticam, - /datum/admins/proc/toggledynamicvote, + /datum/admins/proc/toggleMulticam, //CIT + /datum/admins/proc/toggledynamicvote, //CIT /client/proc/cmd_admin_delete, /*delete an instance/object/mob/etc*/ /client/proc/cmd_debug_del_all, /client/proc/toggle_random_events, /client/proc/forcerandomrotate, /client/proc/adminchangemap, - /client/proc/toggle_hub + /client/proc/panicbunker, + // /client/proc/toggle_interviews, + /client/proc/toggle_hub, + /client/proc/toggle_cdn ) -GLOBAL_PROTECT(admin_verbs_server) GLOBAL_LIST_INIT(admin_verbs_debug, world.AVerbsDebug()) +GLOBAL_PROTECT(admin_verbs_debug) /world/proc/AVerbsDebug() return list( /client/proc/restart_controller, @@ -174,27 +183,37 @@ GLOBAL_LIST_INIT(admin_verbs_debug, world.AVerbsDebug()) /client/proc/cmd_display_init_log, /client/proc/cmd_display_overlay_log, /client/proc/reload_configuration, + // /client/proc/atmos_control, + // /client/proc/reload_cards, + // /client/proc/validate_cards, + // /client/proc/test_cardpack_distribution, + // /client/proc/print_cards, + // #ifdef TESTING + // /client/proc/check_missing_sprites, + // #endif /datum/admins/proc/create_or_modify_area, #ifdef REFERENCE_TRACKING /datum/admins/proc/view_refs, /datum/admins/proc/view_del_failures, #endif - /client/proc/generate_wikichem_list, //DO NOT PRESS UNLESS YOU WANT SUPERLAG + // /client/proc/check_timer_sources, + /client/proc/toggle_cdn, + /client/proc/generate_wikichem_list //DO NOT PRESS UNLESS YOU WANT SUPERLAG ) -GLOBAL_PROTECT(admin_verbs_debug) GLOBAL_LIST_INIT(admin_verbs_possess, list(/proc/possess, /proc/release)) GLOBAL_PROTECT(admin_verbs_possess) GLOBAL_LIST_INIT(admin_verbs_permissions, list(/client/proc/edit_admin_permissions)) GLOBAL_PROTECT(admin_verbs_permissions) GLOBAL_LIST_INIT(admin_verbs_poll, list(/client/proc/create_poll)) +GLOBAL_PROTECT(admin_verbs_poll) //verbs which can be hidden - needs work -GLOBAL_PROTECT(admin_verbs_poll) GLOBAL_LIST_INIT(admin_verbs_hideable, list( /client/proc/set_ooc, /client/proc/reset_ooc, /client/proc/deadmin, /datum/admins/proc/show_traitor_panel, + // /datum/admins/proc/show_skill_panel, /datum/admins/proc/toggleenter, /datum/admins/proc/toggleguests, /datum/admins/proc/announce, @@ -239,7 +258,7 @@ GLOBAL_LIST_INIT(admin_verbs_hideable, list( /client/proc/Debug2, /client/proc/reload_admins, /client/proc/cmd_debug_make_powernets, - /client/proc/startSinglo, + /client/proc/startSinglo, // tg removed this /client/proc/cmd_debug_mob_lists, /client/proc/cmd_debug_del_all, /client/proc/enable_debug_verbs, @@ -247,8 +266,9 @@ GLOBAL_LIST_INIT(admin_verbs_hideable, list( /proc/release, /client/proc/reload_admins, /client/proc/panicbunker, - /client/proc/addbunkerbypass, - /client/proc/revokebunkerbypass, + /client/proc/addbunkerbypass, //CIT + /client/proc/revokebunkerbypass, //CIT + // /client/proc/toggle_interviews, /client/proc/admin_change_sec_level, /client/proc/toggle_nuke, /client/proc/cmd_display_del_log, @@ -322,7 +342,7 @@ GLOBAL_PROTECT(admin_verbs_hideable) remove_verb(src, /client/proc/hide_most_verbs) add_verb(src, /client/proc/show_verbs) - to_chat(src, "Most of your adminverbs have been hidden.") + to_chat(src, "Most of your adminverbs have been hidden.", confidential = TRUE) SSblackbox.record_feedback("tally", "admin_verb", 1, "Hide Most Adminverbs") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! return @@ -333,7 +353,7 @@ GLOBAL_PROTECT(admin_verbs_hideable) remove_admin_verbs() add_verb(src, /client/proc/show_verbs) - to_chat(src, "Almost all of your adminverbs have been hidden.") + to_chat(src, "Almost all of your adminverbs have been hidden.", confidential = TRUE) SSblackbox.record_feedback("tally", "admin_verb", 1, "Hide All Adminverbs") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! return @@ -344,7 +364,7 @@ GLOBAL_PROTECT(admin_verbs_hideable) remove_verb(src, /client/proc/show_verbs) add_admin_verbs() - to_chat(src, "All of your adminverbs are now visible.") + to_chat(src, "All of your adminverbs are now visible.", confidential = TRUE) SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Adminverbs") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! @@ -354,7 +374,8 @@ GLOBAL_PROTECT(admin_verbs_hideable) set category = "Admin.Game" set name = "Aghost" if(!holder) - return FALSE + return + . = TRUE if(isobserver(mob)) //re-enter var/mob/dead/observer/ghost = mob @@ -367,7 +388,7 @@ GLOBAL_PROTECT(admin_verbs_hideable) ghost.reenter_corpse() SSblackbox.record_feedback("tally", "admin_verb", 1, "Admin Reenter") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! else if(isnewplayer(mob)) - to_chat(src, "Error: Aghost: Can't admin-ghost whilst in the lobby. Join or Observe first.") + to_chat(src, "Error: Aghost: Can't admin-ghost whilst in the lobby. Join or Observe first.", confidential = TRUE) return FALSE else //ghostize @@ -375,10 +396,10 @@ GLOBAL_PROTECT(admin_verbs_hideable) message_admins("[key_name_admin(usr)] admin ghosted.") var/mob/body = mob body.ghostize(1, voluntary = TRUE) + init_verbs() if(body && !body.key) body.key = "@[key]" //Haaaaaaaack. But the people have spoken. If it breaks; blame adminbus SSblackbox.record_feedback("tally", "admin_verb", 1, "Admin Ghost") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - return TRUE /client/proc/invisimin() set name = "Invisimin" @@ -387,10 +408,10 @@ GLOBAL_PROTECT(admin_verbs_hideable) if(holder && mob) if(mob.invisibility == INVISIBILITY_OBSERVER) mob.invisibility = initial(mob.invisibility) - to_chat(mob, "Invisimin off. Invisibility reset.") + to_chat(mob, "Invisimin off. Invisibility reset.", confidential = TRUE) else mob.invisibility = INVISIBILITY_OBSERVER - to_chat(mob, "Invisimin on. You are now as invisible as a ghost.") + to_chat(mob, "Invisimin on. You are now as invisible as a ghost.", confidential = TRUE) /client/proc/check_antagonists() set name = "Check Antagonists" @@ -403,8 +424,10 @@ GLOBAL_PROTECT(admin_verbs_hideable) SSblackbox.record_feedback("tally", "admin_verb", 1, "Check Antagonists") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! /client/proc/unban_panel() - set name = "Unban Panel" + set name = "Unbanning Panel" set category = "Admin" + if(!check_rights(R_BAN)) + return if(holder) if(CONFIG_GET(flag/ban_legacy_system)) holder.unbanpanel() @@ -412,6 +435,14 @@ GLOBAL_PROTECT(admin_verbs_hideable) holder.DB_ban_panel() SSblackbox.record_feedback("tally", "admin_verb", 1, "Unban Panel") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! +// /client/proc/ban_panel() +// set name = "Banning Panel" +// set category = "Admin" +// if(!check_rights(R_BAN)) +// return +// holder.ban_panel() +// SSblackbox.record_feedback("tally", "admin_verb", 1, "Banning Panel") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + /client/proc/game_panel() set name = "Game Panel" set category = "Admin.Game" @@ -419,13 +450,13 @@ GLOBAL_PROTECT(admin_verbs_hideable) holder.Game() SSblackbox.record_feedback("tally", "admin_verb", 1, "Game Panel") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! -/client/proc/secrets() - set name = "Secrets" - set category = "Admin.Game" - if (holder) - holder.Secrets() - 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! - +// /client/proc/poll_panel() +// set name = "Server Poll Management" +// set category = "Admin" +// if(!check_rights(R_POLL)) +// return +// holder.poll_list_panel() +// SSblackbox.record_feedback("tally", "admin_verb", 1, "Server Poll Management") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! /client/proc/findStealthKey(txt) if(txt) @@ -457,7 +488,10 @@ GLOBAL_PROTECT(admin_verbs_hideable) if(isobserver(mob)) mob.invisibility = initial(mob.invisibility) mob.alpha = initial(mob.alpha) - mob.name = initial(mob.name) + if(mob.mind) + mob.name = mob.mind.name + else + mob.name = mob.real_name mob.mouse_opacity = initial(mob.mouse_opacity) else var/new_key = ckeyEx(stripped_input(usr, "Enter your desired display name.", "Fake Key", key, 26)) @@ -485,7 +519,7 @@ GLOBAL_PROTECT(admin_verbs_hideable) switch(choice) if(null) - return 0 + return if("Small Bomb (1, 2, 3, 3)") explosion(epicenter, 1, 2, 3, 3, TRUE, TRUE) if("Medium Bomb (2, 3, 4, 4)") @@ -538,7 +572,7 @@ GLOBAL_PROTECT(admin_verbs_hideable) if (isnull(ex_power)) return var/range = round((2 * ex_power)**GLOB.DYN_EX_SCALE) - to_chat(usr, "Estimated Explosive Range: (Devastation: [round(range*0.25)], Heavy: [round(range*0.5)], Light: [round(range)])") + to_chat(usr, "Estimated Explosive Range: (Devastation: [round(range*0.25)], Heavy: [round(range*0.5)], Light: [round(range)])", confidential = TRUE) /client/proc/get_dynex_power() set category = "Debug" @@ -549,7 +583,7 @@ GLOBAL_PROTECT(admin_verbs_hideable) if (isnull(ex_range)) return var/power = (0.5 * ex_range)**(1/GLOB.DYN_EX_SCALE) - to_chat(usr, "Estimated Explosive Power: [power]") + to_chat(usr, "Estimated Explosive Power: [power]", confidential = TRUE) /client/proc/set_dynex_scale() set category = "Debug" @@ -563,6 +597,55 @@ GLOBAL_PROTECT(admin_verbs_hideable) log_admin("[key_name(usr)] has modified Dynamic Explosion Scale: [ex_scale]") message_admins("[key_name_admin(usr)] has modified Dynamic Explosion Scale: [ex_scale]") +// /client/proc/atmos_control() +// set name = "Atmos Control Panel" +// set category = "Debug" +// if(!check_rights(R_DEBUG)) +// return +// SSair.ui_interact(mob) + +// /client/proc/reload_cards() +// set name = "Reload Cards" +// set category = "Debug" +// if(!check_rights(R_DEBUG)) +// return +// if(!SStrading_card_game.loaded) +// message_admins("The card subsystem is not currently loaded") +// return +// reloadAllCardFiles(SStrading_card_game.card_files, SStrading_card_game.card_directory) + +// /client/proc/validate_cards() +// set name = "Validate Cards" +// set category = "Debug" +// if(!check_rights(R_DEBUG)) +// return +// if(!SStrading_card_game.loaded) +// message_admins("The card subsystem is not currently loaded") +// return +// var/message = checkCardpacks(SStrading_card_game.card_packs) +// message += checkCardDatums() +// if(message) +// message_admins(message) + +// /client/proc/test_cardpack_distribution() +// set name = "Test Cardpack Distribution" +// set category = "Debug" +// if(!check_rights(R_DEBUG)) +// return +// if(!SStrading_card_game.loaded) +// message_admins("The card subsystem is not currently loaded") +// return +// var/pack = input("Which pack should we test?", "You fucked it didn't you") as null|anything in sortList(SStrading_card_game.card_packs) +// var/batchCount = input("How many times should we open it?", "Don't worry, I understand") as null|num +// var/batchSize = input("How many cards per batch?", "I hope you remember to check the validation") as null|num +// var/guar = input("Should we use the pack's guaranteed rarity? If so, how many?", "We've all been there. Man you should have seen the old system") as null|num +// checkCardDistribution(pack, batchSize, batchCount, guar) + +// /client/proc/print_cards() +// set name = "Print Cards" +// set category = "Debug" +// printAllCards() + /client/proc/give_spell(mob/T in GLOB.mob_list) set category = "Admin.Fun" set name = "Give Spell" @@ -572,13 +655,13 @@ GLOBAL_PROTECT(admin_verbs_hideable) var/type_length = length_char("/obj/effect/proc_holder/spell") + 2 for(var/A in GLOB.spells) spell_list[copytext_char("[A]", type_length)] = A - var/obj/effect/proc_holder/spell/S = input("Choose the spell to give to that guy", "ABRAKADABRA") as null|anything in spell_list + var/obj/effect/proc_holder/spell/S = input("Choose the spell to give to that guy", "ABRAKADABRA") as null|anything in sortList(spell_list) if(!S) return SSblackbox.record_feedback("tally", "admin_verb", 1, "Give Spell") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! log_admin("[key_name(usr)] gave [key_name(T)] the spell [S].") - message_admins("[key_name_admin(usr)] gave [key_name(T)] the spell [S].") + message_admins("[key_name_admin(usr)] gave [key_name_admin(T)] the spell [S].") S = spell_list[S] if(T.mind) @@ -592,12 +675,12 @@ GLOBAL_PROTECT(admin_verbs_hideable) set name = "Remove Spell" set desc = "Remove a spell from the selected mob." - if(T && T.mind) - var/obj/effect/proc_holder/spell/S = input("Choose the spell to remove", "NO ABRAKADABRA") as null|anything in T.mind.spell_list + if(T?.mind) + var/obj/effect/proc_holder/spell/S = input("Choose the spell to remove", "NO ABRAKADABRA") as null|anything in sortList(T.mind.spell_list) if(S) T.mind.RemoveSpell(S) log_admin("[key_name(usr)] removed the spell [S] from [key_name(T)].") - message_admins("[key_name_admin(usr)] removed the spell [S] from [key_name(T)].") + message_admins("[key_name_admin(usr)] removed the spell [S] from [key_name_admin(T)].") SSblackbox.record_feedback("tally", "admin_verb", 1, "Remove Spell") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! /client/proc/give_disease(mob/living/T in GLOB.mob_living_list) @@ -605,15 +688,15 @@ GLOBAL_PROTECT(admin_verbs_hideable) set name = "Give Disease" set desc = "Gives a Disease to a mob." if(!istype(T)) - to_chat(src, "You can only give a disease to a mob of type /mob/living.") + to_chat(src, "You can only give a disease to a mob of type /mob/living.", confidential = TRUE) return - var/datum/disease/D = input("Choose the disease to give to that guy", "ACHOO") as null|anything in SSdisease.diseases + var/datum/disease/D = input("Choose the disease to give to that guy", "ACHOO") as null|anything in sortList(SSdisease.diseases, /proc/cmp_typepaths_asc) if(!D) return T.ForceContractDisease(new D, FALSE, TRUE) SSblackbox.record_feedback("tally", "admin_verb", 1, "Give Disease") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! log_admin("[key_name(usr)] gave [key_name(T)] the disease [D].") - message_admins("[key_name_admin(usr)] gave [key_name(T)] the disease [D].") + message_admins("[key_name_admin(usr)] gave [key_name_admin(T)] the disease [D].") /client/proc/object_say(obj/O in world) set category = "Admin.Events" @@ -655,8 +738,8 @@ GLOBAL_PROTECT(admin_verbs_hideable) holder.deactivate() to_chat(src, "You are now a normal player.") - log_admin("[src] deadmined themself.") - message_admins("[src] deadmined themself.") + log_admin("[src] deadminned themselves.") + message_admins("[src] deadminned themselves.") SSblackbox.record_feedback("tally", "admin_verb", 1, "Deadmin") /client/proc/readmin() @@ -679,7 +762,7 @@ GLOBAL_PROTECT(admin_verbs_hideable) if (!holder) return //This can happen if an admin attempts to vv themself into somebody elses's deadmin datum by getting ref via brute force - to_chat(src, "You are now an admin.") + to_chat(src, "You are now an admin.", confidential = TRUE) message_admins("[src] re-adminned themselves.") log_admin("[src] re-adminned themselves.") SSblackbox.record_feedback("tally", "admin_verb", 1, "Readmin") diff --git a/code/modules/admin/banjob.dm b/code/modules/admin/banjob.dm index cae1c6b22e..8ebef00508 100644 --- a/code/modules/admin/banjob.dm +++ b/code/modules/admin/banjob.dm @@ -3,8 +3,11 @@ if(!M || !istype(M) || !M.ckey) return FALSE - if(!M.client) //no cache. fallback to a datum/DBQuery - var/datum/DBQuery/query_jobban_check_ban = SSdbcore.NewQuery("SELECT reason FROM [format_table_name("ban")] WHERE ckey = '[sanitizeSQL(M.ckey)]' AND (bantype = 'JOB_PERMABAN' OR (bantype = 'JOB_TEMPBAN' AND expiration_time > Now())) AND isnull(unbanned) AND job = '[sanitizeSQL(rank)]'") + if(!M.client) //no cache. fallback to a datum/db_query + var/datum/db_query/query_jobban_check_ban = SSdbcore.NewQuery({" + SELECT reason 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 = :rank + "}, list("ckey" = M.ckey, "rank" = rank)) if(!query_jobban_check_ban.warn_execute()) qdel(query_jobban_check_ban) return @@ -28,7 +31,10 @@ return if(C && istype(C)) C.jobbancache = list() - var/datum/DBQuery/query_jobban_build_cache = SSdbcore.NewQuery("SELECT job, reason FROM [format_table_name("ban")] WHERE ckey = '[sanitizeSQL(C.ckey)]' AND (bantype = 'JOB_PERMABAN' OR (bantype = 'JOB_TEMPBAN' AND expiration_time > Now())) AND isnull(unbanned)") + var/datum/db_query/query_jobban_build_cache = SSdbcore.NewQuery({" + SELECT job, reason FROM [format_table_name("ban")] + WHERE ckey = :ckey AND (bantype = 'JOB_PERMABAN' OR (bantype = 'JOB_TEMPBAN' AND expiration_time > Now())) AND isnull(unbanned) + "}, list("ckey" = C.ckey)) if(!query_jobban_build_cache.warn_execute()) qdel(query_jobban_build_cache) return diff --git a/code/modules/admin/callproc/callproc.dm b/code/modules/admin/callproc/callproc.dm index c13353b7cd..21bf732493 100644 --- a/code/modules/admin/callproc/callproc.dm +++ b/code/modules/admin/callproc/callproc.dm @@ -1,4 +1,3 @@ - /client/proc/callproc() set category = "Debug" set name = "Advanced ProcCall" @@ -21,7 +20,7 @@ return target = value["value"] if(!istype(target)) - to_chat(usr, "Invalid target.") + to_chat(usr, "Invalid target.", confidential = TRUE) return if("No") target = null @@ -41,12 +40,12 @@ if(targetselected) if(!hascall(target, procname)) - to_chat(usr, "Error: callproc(): type [target.type] has no [proctype] named [procpath].") + to_chat(usr, "Error: callproc(): type [target.type] has no [proctype] named [procpath].", confidential = TRUE) return else procpath = "/[proctype]/[procname]" if(!text2path(procpath)) - to_chat(usr, "Error: callproc(): [procpath] does not exist.") + to_chat(usr, "Error: callproc(): [procpath] does not exist.", confidential = TRUE) return var/list/lst = get_callproc_args() @@ -55,24 +54,24 @@ if(targetselected) if(!target) - to_chat(usr, "Error: callproc(): owner of proc no longer exists.") + to_chat(usr, "Error: callproc(): owner of proc no longer exists.", confidential = TRUE) return var/msg = "[key_name(src)] called [target]'s [procname]() with [lst.len ? "the arguments [list2params(lst)]":"no arguments"]." log_admin(msg) - message_admins(msg) //Proccall announce removed. + message_admins(msg) //Proccall announce removed. admin_ticket_log(target, msg) returnval = WrapAdminProcCall(target, procname, lst) // Pass the lst as an argument list to the proc else //this currently has no hascall protection. wasn't able to get it working. log_admin("[key_name(src)] called [procname]() with [lst.len ? "the arguments [list2params(lst)]":"no arguments"].") - message_admins("[key_name(src)] called [procname]() with [lst.len ? "the arguments [list2params(lst)]":"no arguments"].") //Proccall announce removed. + message_admins("[key_name(src)] called [procname]() with [lst.len ? "the arguments [list2params(lst)]":"no arguments"].") //Proccall announce removed. returnval = WrapAdminProcCall(GLOBAL_PROC, procname, lst) // Pass the lst as an argument list to the proc SSblackbox.record_feedback("tally", "admin_verb", 1, "Advanced ProcCall") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! if(get_retval) get_retval += returnval . = get_callproc_returnval(returnval, procname) if(.) - to_chat(usr, .) + to_chat(usr, ., confidential = TRUE) GLOBAL_VAR(AdminProcCaller) GLOBAL_PROTECT(AdminProcCaller) @@ -84,34 +83,30 @@ GLOBAL_VAR(LastAdminCalledTarget) GLOBAL_PROTECT(LastAdminCalledTarget) GLOBAL_VAR(LastAdminCalledProc) GLOBAL_PROTECT(LastAdminCalledProc) -GLOBAL_LIST_EMPTY(AdminProcCallSpamPrevention) -GLOBAL_PROTECT(AdminProcCallSpamPrevention) +/// Wrapper for proccalls where the datum is flagged as vareditted /proc/WrapAdminProcCall(datum/target, procname, list/arguments) - if(target != GLOBAL_PROC && procname == "Del") - to_chat(usr, "Calling Del() is not allowed") + if(target && procname == "Del") + to_chat(usr, "Calling Del() is not allowed", confidential = TRUE) return if(target != GLOBAL_PROC && !target.CanProcCall(procname)) - to_chat(usr, "Proccall on [target.type]/proc/[procname] is disallowed!") + to_chat(usr, "Proccall on [target.type]/proc/[procname] is disallowed!", confidential = TRUE) return var/current_caller = GLOB.AdminProcCaller var/ckey = usr ? usr.client.ckey : GLOB.AdminProcCaller if(!ckey) CRASH("WrapAdminProcCall with no ckey: [target] [procname] [english_list(arguments)]") + if(current_caller && current_caller != ckey) - if(!GLOB.AdminProcCallSpamPrevention[ckey]) - to_chat(usr, "Another set of admin called procs are still running, your proc will be run after theirs finish.") - GLOB.AdminProcCallSpamPrevention[ckey] = TRUE - UNTIL(!GLOB.AdminProcCaller) - to_chat(usr, "Running your proc") - GLOB.AdminProcCallSpamPrevention -= ckey - else - UNTIL(!GLOB.AdminProcCaller) + // hey kev i removed the sleep here because it blocks this proc + to_chat(usr, "Another set of admin called procs are still running. Try again later.", confidential = TRUE) + return + GLOB.LastAdminCalledProc = procname if(target != GLOBAL_PROC) - GLOB.LastAdminCalledTargetRef = "[REF(target)]" - GLOB.AdminProcCaller = ckey //if this runtimes, too bad for you + GLOB.LastAdminCalledTargetRef = REF(target) + GLOB.AdminProcCaller = ckey //if this runtimes, too bad for you ++GLOB.AdminProcCallCount . = world.WrapAdminProcCall(target, procname, arguments) if(--GLOB.AdminProcCallCount == 0) @@ -120,11 +115,11 @@ GLOBAL_PROTECT(AdminProcCallSpamPrevention) //adv proc call this, ya nerds /world/proc/WrapAdminProcCall(datum/target, procname, list/arguments) if(target == GLOBAL_PROC) - return call(text2path("/proc/[procname]"))(arglist(arguments)) + return call("/proc/[procname]")(arglist(arguments)) else if(target != world) return call(target, procname)(arglist(arguments)) else - log_admin_private("[key_name(usr)] attempted to call world/proc/[procname] with arguments: [english_list(arguments)]") + log_admin("[key_name(usr)] attempted to call world/proc/[procname] with arguments: [english_list(arguments)]") /proc/IsAdminAdvancedProcCall() #ifdef TESTING @@ -145,17 +140,17 @@ GLOBAL_PROTECT(AdminProcCallSpamPrevention) if(!procname) return if(!hascall(A,procname)) - to_chat(usr, "Error: callproc_datum(): type [A.type] has no proc named [procname].") + to_chat(usr, "Error: callproc_datum(): type [A.type] has no proc named [procname].", confidential = TRUE) return var/list/lst = get_callproc_args() if(!lst) return if(!A || !IsValidSrc(A)) - to_chat(usr, "Error: callproc_datum(): owner of proc no longer exists.") + to_chat(usr, "Error: callproc_datum(): owner of proc no longer exists.", confidential = TRUE) return + log_admin("[key_name(src)] called [A]'s [procname]() with [lst.len ? "the arguments [list2params(lst)]":"no arguments"].") var/msg = "[key_name(src)] called [A]'s [procname]() with [lst.len ? "the arguments [list2params(lst)]":"no arguments"]." - log_admin(msg) message_admins(msg) admin_ticket_log(A, msg) SSblackbox.record_feedback("tally", "admin_verb", 1, "Atom ProcCall") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! @@ -163,7 +158,7 @@ GLOBAL_PROTECT(AdminProcCallSpamPrevention) var/returnval = WrapAdminProcCall(A, procname, lst) // Pass the lst as an argument list to the proc . = get_callproc_returnval(returnval,procname) if(.) - to_chat(usr, .) + to_chat(usr, ., confidential = TRUE) /client/proc/get_callproc_args() var/argnum = input("Number of arguments","Number:",0) as num|null @@ -188,7 +183,7 @@ GLOBAL_PROTECT(AdminProcCallSpamPrevention) . = "" if(islist(returnval)) var/list/returnedlist = returnval - . = "" + . = "" if(returnedlist.len) var/assoc_check = returnedlist[1] if(istext(assoc_check) && (returnedlist[assoc_check] != null)) @@ -202,7 +197,7 @@ GLOBAL_PROTECT(AdminProcCallSpamPrevention) . += "\n[elem]" else . = "[procname] returned an empty list" - . += "" + . += "" else - . = "[procname] returned: [!isnull(returnval) ? returnval : "null"]" + . = "[procname] returned: [!isnull(returnval) ? html_encode(returnval) : "null"]" diff --git a/code/modules/admin/create_poll.dm b/code/modules/admin/create_poll.dm index 9f002f92e0..e5391f0f43 100644 --- a/code/modules/admin/create_poll.dm +++ b/code/modules/admin/create_poll.dm @@ -34,8 +34,9 @@ var/endtime = input("Set end time for poll as format YYYY-MM-DD HH:MM:SS. All times in server time. HH:MM:SS is optional and 24-hour. Must be later than starting time for obvious reasons.", "Set end time", SQLtime()) as text if(!endtime) return - endtime = sanitizeSQL(endtime) - var/datum/DBQuery/query_validate_time = SSdbcore.NewQuery("SELECT IF(STR_TO_DATE('[endtime]','%Y-%c-%d %T') > NOW(), STR_TO_DATE('[endtime]','%Y-%c-%d %T'), 0)") + var/datum/db_query/query_validate_time = SSdbcore.NewQuery({" + SELECT IF(STR_TO_DATE(:endtime,'%Y-%c-%d %T') > NOW(), STR_TO_DATE(:endtime,'%Y-%c-%d %T'), 0) + "}, list("endtime" = endtime)) if(!query_validate_time.warn_execute() || QDELETED(usr) || !src) qdel(query_validate_time) return @@ -63,11 +64,9 @@ dontshow = 0 else return - var/sql_ckey = sanitizeSQL(ckey) var/question = input("Write your question","Question") as message|null if(!question) return - question = sanitizeSQL(question) var/list/sql_option_list = list() if(polltype != POLLTYPE_TEXT) var/add_option = 1 @@ -75,7 +74,6 @@ var/option = input("Write your option","Option") as message|null if(!option) return - option = sanitizeSQL(option) var/default_percentage_calc = 0 if(polltype != POLLTYPE_IRV) switch(alert("Should this option be included by default when poll result percentages are generated?",,"Yes","No","Cancel")) @@ -92,34 +90,27 @@ var/descmax = "" if(polltype == POLLTYPE_RATING) minval = input("Set minimum rating value.","Minimum rating") as num|null - if(minval) - minval = sanitizeSQL(minval) - else if(minval == null) + if(minval == null) return maxval = input("Set maximum rating value.","Maximum rating") as num|null - if(maxval) - maxval = sanitizeSQL(maxval) if(minval >= maxval) to_chat(src, "Maximum rating value can't be less than or equal to minimum rating value") continue - else if(maxval == null) + if(maxval == null) return descmin = input("Optional: Set description for minimum rating","Minimum rating description") as message|null - if(descmin) - descmin = sanitizeSQL(descmin) - else if(descmin == null) + if(descmin == null) return descmid = input("Optional: Set description for median rating","Median rating description") as message|null - if(descmid) - descmid = sanitizeSQL(descmid) - else if(descmid == null) + if(descmid == null) return descmax = input("Optional: Set description for maximum rating","Maximum rating description") as message|null - if(descmax) - descmax = sanitizeSQL(descmax) - else if(descmax == null) + if(descmax == null) return - sql_option_list += list(list("text" = "'[option]'", "minval" = "'[minval]'", "maxval" = "'[maxval]'", "descmin" = "'[descmin]'", "descmid" = "'[descmid]'", "descmax" = "'[descmax]'", "default_percentage_calc" = "'[default_percentage_calc]'")) + sql_option_list += list(list( + "text" = option, "minval" = minval, "maxval" = maxval, + "descmin" = descmin, "descmid" = descmid, "descmax" = descmax, + "default_percentage_calc" = default_percentage_calc)) switch(alert(" ",,"Add option","Finish", "Cancel")) if("Add option") add_option = 1 @@ -129,14 +120,21 @@ return 0 var/m1 = "[key_name(usr)] has created a new server poll. Poll type: [polltype] - Admin Only: [adminonly ? "Yes" : "No"] - Question: [question]" var/m2 = "[key_name_admin(usr)] has created a new server poll. Poll type: [polltype] - Admin Only: [adminonly ? "Yes" : "No"]
Question: [question]" - var/datum/DBQuery/query_polladd_question = SSdbcore.NewQuery("INSERT INTO [format_table_name("poll_question")] (polltype, starttime, endtime, question, adminonly, multiplechoiceoptions, createdby_ckey, createdby_ip, dontshow) VALUES ('[polltype]', '[starttime]', '[endtime]', '[question]', '[adminonly]', '[choice_amount]', '[sql_ckey]', INET_ATON('[address]'), '[dontshow]')") + var/datum/db_query/query_polladd_question = SSdbcore.NewQuery({" + INSERT INTO [format_table_name("poll_question")] (polltype, starttime, endtime, question, adminonly, multiplechoiceoptions, createdby_ckey, createdby_ip, dontshow) + VALUES (:polltype, :starttime, :endtime, :question, :adminonly, :choice_amount, :ckey, INET_ATON(:address), :dontshow) + "}, list( + "polltype" = polltype, "starttime" = starttime, "endtime" = endtime, + "question" = question, "adminonly" = adminonly, "choice_amount" = choice_amount, + "ckey" = ckey, "address" = address, "dontshow" = dontshow + )) if(!query_polladd_question.warn_execute()) qdel(query_polladd_question) return qdel(query_polladd_question) if(polltype != POLLTYPE_TEXT) var/pollid = 0 - var/datum/DBQuery/query_get_id = SSdbcore.NewQuery("SELECT LAST_INSERT_ID()") + var/datum/db_query/query_get_id = SSdbcore.NewQuery("SELECT LAST_INSERT_ID()") if(!query_get_id.warn_execute()) qdel(query_get_id) return @@ -145,6 +143,6 @@ qdel(query_get_id) for(var/list/i in sql_option_list) i |= list("pollid" = "'[pollid]'") - SSdbcore.MassInsert(format_table_name("poll_option"), sql_option_list, warn = 1) + SSdbcore.MassInsert(format_table_name("poll_option"), sql_option_list, warn = TRUE) log_admin(m1) message_admins(m2) diff --git a/code/modules/admin/holder2.dm b/code/modules/admin/holder2.dm index 8f4a9742ea..1430d5d77a 100644 --- a/code/modules/admin/holder2.dm +++ b/code/modules/admin/holder2.dm @@ -28,6 +28,8 @@ GLOBAL_PROTECT(href_token) var/deadmined + var/datum/filter_editor/filteriffic + /datum/admins/CanProcCall(procname) . = ..() if(!check_rights(R_SENSITIVE)) diff --git a/code/modules/admin/ipintel.dm b/code/modules/admin/ipintel.dm index e0056b3e40..71c9a11acd 100644 --- a/code/modules/admin/ipintel.dm +++ b/code/modules/admin/ipintel.dm @@ -29,27 +29,27 @@ return if (!bypasscache) var/datum/ipintel/cachedintel = SSipintel.cache[ip] - if (cachedintel && cachedintel.is_valid()) + if (cachedintel?.is_valid()) cachedintel.cache = TRUE return cachedintel if(SSdbcore.Connect()) var/rating_bad = CONFIG_GET(number/ipintel_rating_bad) - var/datum/DBQuery/query_get_ip_intel = SSdbcore.NewQuery({" + var/datum/db_query/query_get_ip_intel = SSdbcore.NewQuery({" SELECT date, intel, TIMESTAMPDIFF(MINUTE,date,NOW()) FROM [format_table_name("ipintel")] WHERE - ip = INET_ATON('[ip]') + ip = INET_ATON(':ip') AND (( - intel < [rating_bad] + intel < :rating_bad AND - date + INTERVAL [CONFIG_GET(number/ipintel_save_good)] HOUR > NOW() + date + INTERVAL :save_good HOUR > NOW() ) OR ( - intel >= [rating_bad] + intel >= :rating_bad AND - date + INTERVAL [CONFIG_GET(number/ipintel_save_bad)] HOUR > NOW() + date + INTERVAL :save_bad HOUR > NOW() )) - "}) + "}, list("ip" = ip, "rating_bad" = rating_bad, "save_good" = CONFIG_GET(number/ipintel_save_good), "save_bad" = CONFIG_GET(number/ipintel_save_bad))) if(!query_get_ip_intel.Execute()) qdel(query_get_ip_intel) return @@ -67,12 +67,15 @@ if (updatecache && res.intel >= 0) SSipintel.cache[ip] = res if(SSdbcore.Connect()) - var/datum/DBQuery/query_add_ip_intel = SSdbcore.NewQuery("INSERT INTO [format_table_name("ipintel")] (ip, intel) VALUES (INET_ATON('[ip]'), [res.intel]) ON DUPLICATE KEY UPDATE intel = VALUES(intel), date = NOW()") + var/datum/db_query/query_add_ip_intel = SSdbcore.NewQuery( + "INSERT INTO [format_table_name("ipintel")] (ip, intel) VALUES (INET_ATON(:ip), :intel) ON DUPLICATE KEY UPDATE intel = VALUES(intel), date = NOW()", + list("ip" = ip, "intel" = res.intel) + ) query_add_ip_intel.Execute() qdel(query_add_ip_intel) -/proc/ip_intel_query(ip, var/retryed=0) +/proc/ip_intel_query(ip, retryed=0) . = -1 //default if (!ip) return @@ -131,8 +134,3 @@ /proc/log_ipintel(text) log_game("IPINTEL: [text]") debug_admins("IPINTEL: [text]") - - - - - diff --git a/code/modules/admin/permissionedit.dm b/code/modules/admin/permissionedit.dm index 05f7465b03..9f5dc00a48 100644 --- a/code/modules/admin/permissionedit.dm +++ b/code/modules/admin/permissionedit.dm @@ -15,21 +15,14 @@ else output += "
\[Log\]
\[Management\]" if(action == 1) - var/list/searchlist = list(" WHERE ") - if(target) - searchlist += "ckey = '[sanitizeSQL(target)]'" - if(operation) - if(target) - searchlist += " AND " - searchlist += "operation = '[sanitizeSQL(operation)]'" - var/search - if(searchlist.len > 1) - search = searchlist.Join("") var/logcount = 0 var/logssperpage = 20 var/pagecount = 0 page = text2num(page) - var/datum/DBQuery/query_count_admin_logs = SSdbcore.NewQuery("SELECT COUNT(id) FROM [format_table_name("admin_log")][search]") + var/datum/db_query/query_count_admin_logs = SSdbcore.NewQuery( + "SELECT COUNT(id) FROM [format_table_name("admin_log")] WHERE (:target IS NULL OR adminckey = :target) AND (:operation IS NULL OR operation = :operation)", + list("target" = target, "operation" = operation) + ) if(!query_count_admin_logs.warn_execute()) qdel(query_count_admin_logs) return @@ -43,8 +36,20 @@ logcount -= logssperpage pagecount++ output += "|" - var/limit = " LIMIT [logssperpage * page], [logssperpage]" - var/datum/DBQuery/query_search_admin_logs = SSdbcore.NewQuery("SELECT datetime, round_id, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = adminckey), adminckey), operation, IF(ckey IS NULL, target, byond_key), log FROM [format_table_name("admin_log")] LEFT JOIN [format_table_name("player")] ON target = ckey[search] ORDER BY datetime DESC[limit]") + var/datum/db_query/query_search_admin_logs = SSdbcore.NewQuery({" + SELECT + datetime, + round_id, + IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = adminckey), adminckey), + operation, + IF(ckey IS NULL, target, byond_key), + log + FROM [format_table_name("admin_log")] + LEFT JOIN [format_table_name("player")] ON target = ckey + WHERE (:target IS NULL OR ckey = :target) AND (:operation IS NULL OR operation = :operation) + ORDER BY datetime DESC + LIMIT :skip, :take + "}, list("target" = target, "operation" = operation, "skip" = logssperpage * page, "take" = logssperpage)) if(!query_search_admin_logs.warn_execute()) qdel(query_search_admin_logs) return @@ -59,7 +64,7 @@ qdel(query_search_admin_logs) if(action == 2) output += "

Admin ckeys with invalid ranks

" - var/datum/DBQuery/query_check_admin_errors = SSdbcore.NewQuery("SELECT IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("admin")].ckey), ckey), [format_table_name("admin")].rank FROM [format_table_name("admin")] LEFT JOIN [format_table_name("admin_ranks")] ON [format_table_name("admin_ranks")].rank = [format_table_name("admin")].rank WHERE [format_table_name("admin_ranks")].rank IS NULL") + var/datum/db_query/query_check_admin_errors = SSdbcore.NewQuery("SELECT IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("admin")].ckey), ckey), [format_table_name("admin")].`rank` FROM [format_table_name("admin")] LEFT JOIN [format_table_name("admin_ranks")] ON [format_table_name("admin_ranks")].`rank` = [format_table_name("admin")].`rank` WHERE [format_table_name("admin_ranks")].`rank` IS NULL") if(!query_check_admin_errors.warn_execute()) qdel(query_check_admin_errors) return @@ -70,7 +75,7 @@ output += "
" qdel(query_check_admin_errors) output += "

Unused ranks

" - var/datum/DBQuery/query_check_unused_rank = SSdbcore.NewQuery("SELECT [format_table_name("admin_ranks")].rank, flags, exclude_flags, can_edit_flags FROM [format_table_name("admin_ranks")] LEFT JOIN [format_table_name("admin")] ON [format_table_name("admin")].rank = [format_table_name("admin_ranks")].rank WHERE [format_table_name("admin")].rank IS NULL") + var/datum/db_query/query_check_unused_rank = SSdbcore.NewQuery("SELECT [format_table_name("admin_ranks")].`rank`, flags, exclude_flags, can_edit_flags FROM [format_table_name("admin_ranks")] LEFT JOIN [format_table_name("admin")] ON [format_table_name("admin")].`rank` = [format_table_name("admin_ranks")].`rank` WHERE [format_table_name("admin")].`rank` IS NULL") if(!query_check_unused_rank.warn_execute()) qdel(query_check_unused_rank) return @@ -130,7 +135,7 @@ log_admin("[key_name(usr)] attempted to edit admin permissions without sufficient rights.") return if(IsAdminAdvancedProcCall()) - to_chat(usr, "Admin Edit blocked: Advanced ProcCall detected.") + to_chat(usr, "Admin Edit blocked: Advanced ProcCall detected.", confidential = TRUE) return var/datum/asset/permissions_assets = get_asset_datum(/datum/asset/simple/permissions) permissions_assets.send(src) @@ -145,19 +150,19 @@ skip = TRUE if(!CONFIG_GET(flag/admin_legacy_system) && CONFIG_GET(flag/protect_legacy_admins) && task == "rank") if(admin_ckey in GLOB.protected_admins) - to_chat(usr, "Editing the rank of this admin is blocked by server configuration.") + to_chat(usr, "Editing the rank of this admin is blocked by server configuration.", confidential = TRUE) return if(!CONFIG_GET(flag/admin_legacy_system) && CONFIG_GET(flag/protect_legacy_ranks) && task == "permissions") if(D.rank in GLOB.protected_ranks) - to_chat(usr, "Editing the flags of this rank is blocked by server configuration.") + to_chat(usr, "Editing the flags of this rank is blocked by server configuration.", confidential = TRUE) return if(CONFIG_GET(flag/load_legacy_ranks_only) && (task == "add" || task == "rank" || task == "permissions")) - to_chat(usr, "Database rank loading is disabled, only temporary changes can be made to a rank's permissions and permanently creating a new rank is blocked.") + to_chat(usr, "Database rank loading is disabled, only temporary changes can be made to a rank's permissions and permanently creating a new rank is blocked.", confidential = TRUE) legacy_only = TRUE if(check_rights(R_DBRANKS, FALSE)) if(!skip) if(!SSdbcore.Connect()) - to_chat(usr, "Unable to connect to database, changes are temporary only.") + to_chat(usr, "Unable to connect to database, changes are temporary only.", confidential = TRUE) use_db = FALSE else use_db = alert("Permanent changes are saved to the database for future rounds, temporary changes will affect only the current round", "Permanent or Temporary?", "Permanent", "Temporary", "Cancel") @@ -165,7 +170,6 @@ return if(use_db == "Permanent") use_db = TRUE - admin_ckey = sanitizeSQL(admin_ckey) else use_db = FALSE if(QDELETED(usr)) @@ -209,26 +213,34 @@ if(!.) return FALSE if(!admin_ckey && (. in GLOB.admin_datums+GLOB.deadmins)) - to_chat(usr, "[admin_key] is already an admin.") + to_chat(usr, "[admin_key] is already an admin.", confidential = TRUE) return FALSE if(use_db) - . = sanitizeSQL(.) //if an admin exists without a datum they won't be caught by the above - var/datum/DBQuery/query_admin_in_db = SSdbcore.NewQuery("SELECT 1 FROM [format_table_name("admin")] WHERE ckey = '[.]'") + var/datum/db_query/query_admin_in_db = SSdbcore.NewQuery( + "SELECT 1 FROM [format_table_name("admin")] WHERE ckey = :ckey", + list("ckey" = .) + ) if(!query_admin_in_db.warn_execute()) qdel(query_admin_in_db) return FALSE if(query_admin_in_db.NextRow()) qdel(query_admin_in_db) - to_chat(usr, "[admin_key] already listed in admin database. Check the Management tab if they don't appear in the list of admins.") + to_chat(usr, "[admin_key] already listed in admin database. Check the Management tab if they don't appear in the list of admins.", confidential = TRUE) return FALSE qdel(query_admin_in_db) - var/datum/DBQuery/query_add_admin = SSdbcore.NewQuery("INSERT INTO [format_table_name("admin")] (ckey, rank) VALUES ('[.]', 'NEW ADMIN')") + var/datum/db_query/query_add_admin = SSdbcore.NewQuery( + "INSERT INTO [format_table_name("admin")] (ckey, `rank`) VALUES (:ckey, 'NEW ADMIN')", + list("ckey" = .) + ) if(!query_add_admin.warn_execute()) qdel(query_add_admin) return FALSE qdel(query_add_admin) - var/datum/DBQuery/query_add_admin_log = SSdbcore.NewQuery("INSERT INTO [format_table_name("admin_log")] (datetime, round_id, adminckey, adminip, operation, target, log) VALUES ('[SQLtime()]', '[GLOB.round_id]', '[sanitizeSQL(usr.ckey)]', INET_ATON('[sanitizeSQL(usr.client.address)]'), 'add admin', '[.]', 'New admin added: [.]')") + var/datum/db_query/query_add_admin_log = SSdbcore.NewQuery({" + INSERT INTO [format_table_name("admin_log")] (datetime, round_id, adminckey, adminip, operation, target, log) + VALUES (:time, :round_id, :adminckey, INET_ATON(:adminip), 'add admin', :target, CONCAT('New admin added: ', :target)) + "}, list("time" = SQLtime(), "round_id" = "[GLOB.round_id]", "adminckey" = usr.ckey, "adminip" = usr.client.address, "target" = .)) if(!query_add_admin_log.warn_execute()) qdel(query_add_admin_log) return FALSE @@ -243,12 +255,18 @@ var/m1 = "[key_name_admin(usr)] removed [admin_key] from the admins list [use_db ? "permanently" : "temporarily"]" var/m2 = "[key_name(usr)] removed [admin_key] from the admins list [use_db ? "permanently" : "temporarily"]" if(use_db) - var/datum/DBQuery/query_add_rank = SSdbcore.NewQuery("DELETE FROM [format_table_name("admin")] WHERE ckey = '[admin_ckey]'") + var/datum/db_query/query_add_rank = SSdbcore.NewQuery( + "DELETE FROM [format_table_name("admin")] WHERE ckey = :ckey", + list("ckey" = admin_ckey) + ) if(!query_add_rank.warn_execute()) qdel(query_add_rank) return qdel(query_add_rank) - var/datum/DBQuery/query_add_rank_log = SSdbcore.NewQuery("INSERT INTO [format_table_name("admin_log")] (datetime, round_id, adminckey, adminip, operation, target, log) VALUES ('[SQLtime()]', '[GLOB.round_id]', '[sanitizeSQL(usr.ckey)]', INET_ATON('[sanitizeSQL(usr.client.address)]'), 'remove admin', '[admin_ckey]', 'Admin removed: [admin_ckey]')") + var/datum/db_query/query_add_rank_log = SSdbcore.NewQuery({" + INSERT INTO [format_table_name("admin_log")] (datetime, round_id, adminckey, adminip, operation, target, log) + VALUES (:time, :round_id, :adminckey, INET_ATON(:adminip), 'remove admin', :admin_ckey, CONCAT('Admin removed: ', :admin_ckey)) + "}, list("time" = SQLtime(), "round_id" = "[GLOB.round_id]", "adminckey" = usr.ckey, "adminip" = usr.client.address, "admin_ckey" = admin_ckey)) if(!query_add_rank_log.warn_execute()) qdel(query_add_rank_log) return @@ -271,6 +289,14 @@ log_admin("[key_name(usr)] forcefully deadmined [admin_key]") D.deactivate() //after logs so the deadmined admin can see the message. +/datum/admins/proc/auto_deadmin() + to_chat(owner, "You are now a normal player.", confidential = TRUE) + var/old_owner = owner + deactivate() + message_admins("[old_owner] deadmined via auto-deadmin config.") + log_admin("[old_owner] deadmined via auto-deadmin config.") + return TRUE + /datum/admins/proc/change_admin_rank(admin_ckey, admin_key, use_db, datum/admins/D, legacy_only) var/datum/admin_rank/R var/list/rank_names = list() @@ -281,7 +307,7 @@ rank_names[R.name] = R var/new_rank = input("Please select a rank", "New rank") as null|anything in rank_names if(new_rank == "*New Rank*") - new_rank = ckeyEx(input("Please input a new rank", "New custom rank") as text|null) + new_rank = input("Please input a new rank", "New custom rank") as text|null if(!new_rank) return R = rank_names[new_rank] @@ -294,10 +320,12 @@ var/m1 = "[key_name_admin(usr)] edited the admin rank of [admin_key] to [new_rank] [use_db ? "permanently" : "temporarily"]" var/m2 = "[key_name(usr)] edited the admin rank of [admin_key] to [new_rank] [use_db ? "permanently" : "temporarily"]" if(use_db) - new_rank = sanitizeSQL(new_rank) //if a player was tempminned before having a permanent change made to their rank they won't yet be in the db var/old_rank - var/datum/DBQuery/query_admin_in_db = SSdbcore.NewQuery("SELECT rank FROM [format_table_name("admin")] WHERE ckey = '[admin_ckey]'") + var/datum/db_query/query_admin_in_db = SSdbcore.NewQuery( + "SELECT `rank` FROM [format_table_name("admin")] WHERE ckey = :admin_ckey", + list("admin_ckey" = admin_ckey) + ) if(!query_admin_in_db.warn_execute()) qdel(query_admin_in_db) return @@ -308,29 +336,44 @@ old_rank = query_admin_in_db.item[1] qdel(query_admin_in_db) //similarly if a temp rank is created it won't be in the db if someone is permanently changed to it - var/datum/DBQuery/query_rank_in_db = SSdbcore.NewQuery("SELECT 1 FROM [format_table_name("admin_ranks")] WHERE rank = '[new_rank]'") + var/datum/db_query/query_rank_in_db = SSdbcore.NewQuery( + "SELECT 1 FROM [format_table_name("admin_ranks")] WHERE `rank` = :new_rank", + list("new_rank" = new_rank) + ) if(!query_rank_in_db.warn_execute()) qdel(query_rank_in_db) return if(!query_rank_in_db.NextRow()) QDEL_NULL(query_rank_in_db) - var/datum/DBQuery/query_add_rank = SSdbcore.NewQuery("INSERT INTO [format_table_name("admin_ranks")] (rank, flags, exclude_flags, can_edit_flags) VALUES ('[new_rank]', '0', '0', '0')") + var/datum/db_query/query_add_rank = SSdbcore.NewQuery({" + INSERT INTO [format_table_name("admin_ranks")] (`rank`, flags, exclude_flags, can_edit_flags) + VALUES (:new_rank, '0', '0', '0') + "}, list("new_rank" = new_rank)) if(!query_add_rank.warn_execute()) qdel(query_add_rank) return qdel(query_add_rank) - var/datum/DBQuery/query_add_rank_log = SSdbcore.NewQuery("INSERT INTO [format_table_name("admin_log")] (datetime, round_id, adminckey, adminip, operation, target, log) VALUES ('[SQLtime()]', '[GLOB.round_id]', '[sanitizeSQL(usr.ckey)]', INET_ATON('[sanitizeSQL(usr.client.address)]'), 'add rank', '[new_rank]', 'New rank added: [new_rank]')") + var/datum/db_query/query_add_rank_log = SSdbcore.NewQuery({" + INSERT INTO [format_table_name("admin_log")] (datetime, round_id, adminckey, adminip, operation, target, log) + VALUES (:time, :round_id, :adminckey, INET_ATON(:adminip), 'add rank', :new_rank, CONCAT('New rank added: ', :new_rank)) + "}, list("time" = SQLtime(), "round_id" = "[GLOB.round_id]", "adminckey" = usr.ckey, "adminip" = usr.client.address, "new_rank" = new_rank)) if(!query_add_rank_log.warn_execute()) qdel(query_add_rank_log) return qdel(query_add_rank_log) qdel(query_rank_in_db) - var/datum/DBQuery/query_change_rank = SSdbcore.NewQuery("UPDATE [format_table_name("admin")] SET rank = '[new_rank]' WHERE ckey = '[admin_ckey]'") + var/datum/db_query/query_change_rank = SSdbcore.NewQuery( + "UPDATE [format_table_name("admin")] SET `rank` = :new_rank WHERE ckey = :admin_ckey", + list("new_rank" = new_rank, "admin_ckey" = admin_ckey) + ) if(!query_change_rank.warn_execute()) qdel(query_change_rank) return qdel(query_change_rank) - var/datum/DBQuery/query_change_rank_log = SSdbcore.NewQuery("INSERT INTO [format_table_name("admin_log")] (datetime, round_id, adminckey, adminip, operation, target, log) VALUES ('[SQLtime()]', '[GLOB.round_id]', '[sanitizeSQL(usr.ckey)]', INET_ATON('[sanitizeSQL(usr.client.address)]'), 'change admin rank', '[admin_ckey]', 'Rank of [admin_ckey] changed from [old_rank] to [new_rank]')") + var/datum/db_query/query_change_rank_log = SSdbcore.NewQuery({" + INSERT INTO [format_table_name("admin_log")] (datetime, round_id, adminckey, adminip, operation, target, log) + VALUES (:time, :round_id, :adminckey, INET_ATON(:adminip), 'change admin rank', :target, CONCAT('Rank of ', :target, ' changed from ', :old_rank, ' to ', :new_rank)) + "}, list("time" = SQLtime(), "round_id" = "[GLOB.round_id]", "adminckey" = usr.ckey, "adminip" = usr.client.address, "target" = admin_ckey, "old_rank" = old_rank, "new_rank" = new_rank)) if(!query_change_rank_log.warn_execute()) qdel(query_change_rank_log) return @@ -357,11 +400,15 @@ return var/m1 = "[key_name_admin(usr)] edited the permissions of [use_db ? " rank [D.rank.name] permanently" : "[admin_key] temporarily"]" var/m2 = "[key_name(usr)] edited the permissions of [use_db ? " rank [D.rank.name] permanently" : "[admin_key] temporarily"]" - if(use_db || legacy_only) + if(use_db && !legacy_only) + var/rank_name = D.rank.name var/old_flags var/old_exclude_flags var/old_can_edit_flags - var/datum/DBQuery/query_get_rank_flags = SSdbcore.NewQuery("SELECT flags, exclude_flags, can_edit_flags FROM [format_table_name("admin_ranks")] WHERE rank = '[D.rank.name]'") + var/datum/db_query/query_get_rank_flags = SSdbcore.NewQuery( + "SELECT flags, exclude_flags, can_edit_flags FROM [format_table_name("admin_ranks")] WHERE `rank` = :rank_name", + list("rank_name" = rank_name) + ) if(!query_get_rank_flags.warn_execute()) qdel(query_get_rank_flags) return @@ -370,12 +417,19 @@ old_exclude_flags = text2num(query_get_rank_flags.item[2]) old_can_edit_flags = text2num(query_get_rank_flags.item[3]) qdel(query_get_rank_flags) - var/datum/DBQuery/query_change_rank_flags = SSdbcore.NewQuery("UPDATE [format_table_name("admin_ranks")] SET flags = '[new_flags]', exclude_flags = '[new_exclude_flags]', can_edit_flags = '[new_can_edit_flags]' WHERE rank = '[D.rank.name]'") + var/datum/db_query/query_change_rank_flags = SSdbcore.NewQuery( + "UPDATE [format_table_name("admin_ranks")] SET flags = :new_flags, exclude_flags = :new_exclude_flags, can_edit_flags = :new_can_edit_flags WHERE `rank` = :rank_name", + list("new_flags" = new_flags, "new_exclude_flags" = new_exclude_flags, "new_can_edit_flags" = new_can_edit_flags, "rank_name" = rank_name) + ) if(!query_change_rank_flags.warn_execute()) qdel(query_change_rank_flags) return qdel(query_change_rank_flags) - var/datum/DBQuery/query_change_rank_flags_log = SSdbcore.NewQuery("INSERT INTO [format_table_name("admin_log")] (datetime, round_id, adminckey, adminip, operation, target, log) VALUES ('[SQLtime()]', '[GLOB.round_id]', '[sanitizeSQL(usr.ckey)]', INET_ATON('[sanitizeSQL(usr.client.address)]'), 'change rank flags', '[D.rank.name]', 'Permissions of [D.rank.name] changed from[rights2text(old_flags," ")][rights2text(old_exclude_flags," ", "-")][rights2text(old_can_edit_flags," ", "*")] to[rights2text(new_flags," ")][rights2text(new_exclude_flags," ", "-")][rights2text(new_can_edit_flags," ", "*")]')") + var/log_message = "Permissions of [rank_name] changed from[rights2text(old_flags," ")][rights2text(old_exclude_flags," ", "-")][rights2text(old_can_edit_flags," ", "*")] to[rights2text(new_flags," ")][rights2text(new_exclude_flags," ", "-")][rights2text(new_can_edit_flags," ", "*")]" + var/datum/db_query/query_change_rank_flags_log = SSdbcore.NewQuery({" + INSERT INTO [format_table_name("admin_log")] (datetime, round_id, adminckey, adminip, operation, target, log) + VALUES (:time, :round_id, :adminckey, INET_ATON(:adminip), 'change rank flags', :rank_name, :log) + "}, list("time" = SQLtime(), "round_id" = "[GLOB.round_id]", "adminckey" = usr.ckey, "adminip" = usr.client.address, "rank_name" = rank_name, "log" = log_message)) if(!query_change_rank_flags_log.warn_execute()) qdel(query_change_rank_flags_log) return @@ -418,33 +472,41 @@ return for(var/datum/admin_rank/R in GLOB.admin_ranks) if(R.name == admin_rank && (!(R.rights & usr.client.holder.rank.can_edit_rights) == R.rights)) - to_chat(usr, "You don't have edit rights to all the rights this rank has, rank deletion not permitted.") + to_chat(usr, "You don't have edit rights to all the rights this rank has, rank deletion not permitted.", confidential = TRUE) return if(!CONFIG_GET(flag/admin_legacy_system) && CONFIG_GET(flag/protect_legacy_ranks) && (admin_rank in GLOB.protected_ranks)) - to_chat(usr, "Deletion of protected ranks is not permitted, it must be removed from admin_ranks.txt.") + to_chat(usr, "Deletion of protected ranks is not permitted, it must be removed from admin_ranks.txt.", confidential = TRUE) return if(CONFIG_GET(flag/load_legacy_ranks_only)) - to_chat(usr, "Rank deletion not permitted while database rank loading is disabled.") + to_chat(usr, "Rank deletion not permitted while database rank loading is disabled.", confidential = TRUE) return - admin_rank = sanitizeSQL(admin_rank) - var/datum/DBQuery/query_admins_with_rank = SSdbcore.NewQuery("SELECT 1 FROM [format_table_name("admin")] WHERE rank = '[admin_rank]'") + var/datum/db_query/query_admins_with_rank = SSdbcore.NewQuery( + "SELECT 1 FROM [format_table_name("admin")] WHERE `rank` = :admin_rank", + list("admin_rank" = admin_rank) + ) if(!query_admins_with_rank.warn_execute()) qdel(query_admins_with_rank) return if(query_admins_with_rank.NextRow()) qdel(query_admins_with_rank) - to_chat(usr, "Error: Rank deletion attempted while rank still used; Tell a coder, this shouldn't happen.") + to_chat(usr, "Error: Rank deletion attempted while rank still used; Tell a coder, this shouldn't happen.", confidential = TRUE) return qdel(query_admins_with_rank) if(alert("Are you sure you want to remove [admin_rank]?","Confirm Removal","Do it","Cancel") == "Do it") var/m1 = "[key_name_admin(usr)] removed rank [admin_rank] permanently" var/m2 = "[key_name(usr)] removed rank [admin_rank] permanently" - var/datum/DBQuery/query_add_rank = SSdbcore.NewQuery("DELETE FROM [format_table_name("admin_ranks")] WHERE rank = '[admin_rank]'") + var/datum/db_query/query_add_rank = SSdbcore.NewQuery( + "DELETE FROM [format_table_name("admin_ranks")] WHERE `rank` = :admin_rank", + list("admin_rank" = admin_rank) + ) if(!query_add_rank.warn_execute()) qdel(query_add_rank) return qdel(query_add_rank) - var/datum/DBQuery/query_add_rank_log = SSdbcore.NewQuery("INSERT INTO [format_table_name("admin_log")] (datetime, round_id, adminckey, adminip, operation, target, log) VALUES ('[SQLtime()]', '[GLOB.round_id]', '[sanitizeSQL(usr.ckey)]', INET_ATON('[sanitizeSQL(usr.client.address)]'), 'remove rank', '[admin_rank]', 'Rank removed: [admin_rank]')") + var/datum/db_query/query_add_rank_log = SSdbcore.NewQuery({" + INSERT INTO [format_table_name("admin_log")] (datetime, round_id, adminckey, adminip, operation, target, log) + VALUES (:time, :round_id, :adminckey, INET_ATON(:adminip), 'remove rank', :admin_rank, CONCAT('Rank removed: ', :admin_rank)) + "}, list("time" = SQLtime(), "round_id" = "[GLOB.round_id]", "adminckey" = usr.ckey, "adminip" = usr.client.address, "admin_rank" = admin_rank)) if(!query_add_rank_log.warn_execute()) qdel(query_add_rank_log) return @@ -455,11 +517,13 @@ /datum/admins/proc/sync_lastadminrank(admin_ckey, admin_key, datum/admins/D) var/sqlrank = "Player" if (D) - sqlrank = sanitizeSQL(D.rank.name) - admin_ckey = sanitizeSQL(admin_ckey) - var/datum/DBQuery/query_sync_lastadminrank = SSdbcore.NewQuery("UPDATE [format_table_name("player")] SET lastadminrank = '[sqlrank]' WHERE ckey = '[admin_ckey]'") + sqlrank = D.rank.name + var/datum/db_query/query_sync_lastadminrank = SSdbcore.NewQuery( + "UPDATE [format_table_name("player")] SET lastadminrank = :rank WHERE ckey = :ckey", + list("rank" = sqlrank, "ckey" = admin_ckey) + ) if(!query_sync_lastadminrank.warn_execute()) qdel(query_sync_lastadminrank) return qdel(query_sync_lastadminrank) - to_chat(usr, "Sync of [admin_key] successful.") + to_chat(usr, "Sync of [admin_key] successful.", confidential = TRUE) diff --git a/code/modules/admin/sql_message_system.dm b/code/modules/admin/sql_message_system.dm index 4218d4238f..3ba309bc24 100644 --- a/code/modules/admin/sql_message_system.dm +++ b/code/modules/admin/sql_message_system.dm @@ -1,6 +1,6 @@ /proc/create_message(type, target_key, admin_ckey, text, timestamp, server, secret, logged = 1, browse, expiry, note_severity) if(!SSdbcore.Connect()) - to_chat(usr, "Failed to establish database connection.") + to_chat(usr, "Failed to establish database connection.", confidential = TRUE) return if(!type) return @@ -9,8 +9,11 @@ var/new_key = input(usr,"Who would you like to create a [type] for?","Enter a key or ckey",null) as null|text if(!new_key) return - var/new_ckey = sanitizeSQL(ckey(new_key)) - var/datum/DBQuery/query_find_ckey = SSdbcore.NewQuery("SELECT ckey FROM [format_table_name("player")] WHERE ckey = '[new_ckey]'") + var/new_ckey = ckey(new_key) + var/datum/db_query/query_find_ckey = SSdbcore.NewQuery( + "SELECT ckey FROM [format_table_name("player")] WHERE ckey = :ckey", + list("ckey" = new_ckey) + ) if(!query_find_ckey.warn_execute()) qdel(query_find_ckey) return @@ -23,29 +26,24 @@ target_key = new_key if(QDELETED(usr)) return - if(target_ckey) - target_ckey = sanitizeSQL(target_ckey) if(!target_key) target_key = target_ckey if(!admin_ckey) admin_ckey = usr.ckey if(!admin_ckey) return - admin_ckey = sanitizeSQL(admin_ckey) if(!target_ckey) target_ckey = admin_ckey if(!text) text = input(usr,"Write your [type]","Create [type]") as null|message if(!text) return - text = sanitizeSQL(text) if(!timestamp) timestamp = SQLtime() if(!server) var/ssqlname = CONFIG_GET(string/serversqlname) if (ssqlname) server = ssqlname - server = sanitizeSQL(server) if(isnull(secret)) switch(alert("Hide note from being viewed by players?", "Secret note?","Yes","No","Cancel")) if("Yes") @@ -59,15 +57,17 @@ var/expire_time = input("Set expiry time for [type] as format YYYY-MM-DD HH:MM:SS. All times in server time. HH:MM:SS is optional and 24-hour. Must be later than current time for obvious reasons.", "Set expiry time", SQLtime()) as null|text if(!expire_time) return - expire_time = sanitizeSQL(expire_time) - var/datum/DBQuery/query_validate_expire_time = SSdbcore.NewQuery("SELECT IF(STR_TO_DATE('[expire_time]','%Y-%c-%d %T') > NOW(), STR_TO_DATE('[expire_time]','%Y-%c-%d %T'), 0)") + var/datum/db_query/query_validate_expire_time = SSdbcore.NewQuery( + "SELECT IF(STR_TO_DATE(:expire_time,'%Y-%c-%d %T') > NOW(), STR_TO_DATE(:expire_time,'%Y-%c-%d %T'), 0)", + list("expire_time" = expire_time) + ) if(!query_validate_expire_time.warn_execute()) qdel(query_validate_expire_time) return if(query_validate_expire_time.NextRow()) var/checktime = text2num(query_validate_expire_time.item[1]) if(!checktime) - to_chat(usr, "Datetime entered is improperly formatted or not later than current server time.") + to_chat(usr, "Datetime entered is improperly formatted or not later than current server time.", confidential = TRUE) qdel(query_validate_expire_time) return expiry = query_validate_expire_time.item[1] @@ -76,8 +76,23 @@ note_severity = input("Set the severity of the note.", "Severity", null, null) as null|anything in list("High", "Medium", "Minor", "None") if(!note_severity) return - note_severity = sanitizeSQL(note_severity) - var/datum/DBQuery/query_create_message = SSdbcore.NewQuery("INSERT INTO [format_table_name("messages")] (type, targetckey, adminckey, text, timestamp, server, server_ip, server_port, round_id, secret, expire_timestamp, severity) VALUES ('[type]', '[target_ckey]', '[admin_ckey]', '[text]', '[timestamp]', '[server]', INET_ATON(IF('[world.internet_address]' LIKE '', '0', '[world.internet_address]')), '[world.port]', '[GLOB.round_id]','[secret]', [expiry ? "'[expiry]'" : "NULL"], [note_severity ? "'[note_severity]'" : "NULL"])") + var/datum/db_query/query_create_message = SSdbcore.NewQuery({" + INSERT INTO [format_table_name("messages")] (type, targetckey, adminckey, text, timestamp, server, server_ip, server_port, round_id, secret, expire_timestamp, severity) + VALUES (:type, :target_ckey, :admin_ckey, :text, :timestamp, :server, INET_ATON(:internet_address), :port, :round_id, :secret, :expiry, :note_severity) + "}, list( + "type" = type, + "target_ckey" = target_ckey, + "admin_ckey" = admin_ckey, + "text" = text, + "timestamp" = timestamp, + "server" = server, + "internet_address" = world.internet_address || "0", + "port" = "[world.port]", + "round_id" = GLOB.round_id, + "secret" = secret, + "expiry" = expiry || null, + "note_severity" = note_severity, + )) var/pm = "[key_name(usr)] has created a [type][(type == "note" || type == "message" || type == "watchlist entry") ? " for [target_key]" : ""]: [text]" var/header = "[key_name_admin(usr)] has created a [type][(type == "note" || type == "message" || type == "watchlist entry") ? " for [target_key]" : ""]" if(!query_create_message.warn_execute()) @@ -96,7 +111,7 @@ /proc/delete_message(message_id, logged = 1, browse) if(!SSdbcore.Connect()) - to_chat(usr, "Failed to establish database connection.") + to_chat(usr, "Failed to establish database connection.", confidential = TRUE) return message_id = text2num(message_id) if(!message_id) @@ -106,7 +121,11 @@ var/text var/user_key_name = key_name(usr) var/user_name_admin = key_name_admin(usr) - var/datum/DBQuery/query_find_del_message = SSdbcore.NewQuery("SELECT type, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = targetckey), targetckey), text FROM [format_table_name("messages")] WHERE id = [message_id] AND deleted = 0") + var/deleted_by_ckey = usr.ckey + var/datum/db_query/query_find_del_message = SSdbcore.NewQuery( + "SELECT type, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = targetckey), targetckey), text FROM [format_table_name("messages")] WHERE id = :id AND deleted = 0", + list("id" = message_id) + ) if(!query_find_del_message.warn_execute()) qdel(query_find_del_message) return @@ -115,7 +134,10 @@ target_key = query_find_del_message.item[2] text = query_find_del_message.item[3] qdel(query_find_del_message) - var/datum/DBQuery/query_del_message = SSdbcore.NewQuery("UPDATE [format_table_name("messages")] SET deleted = 1 WHERE id = [message_id]") + var/datum/db_query/query_del_message = SSdbcore.NewQuery( + "UPDATE [format_table_name("messages")] SET deleted = 1, deleted_ckey = :deleted_ckey WHERE id = :id", + list("deleted_ckey" = deleted_by_ckey, "id" = message_id) + ) if(!query_del_message.warn_execute()) qdel(query_del_message) return @@ -132,16 +154,24 @@ /proc/edit_message(message_id, browse) if(!SSdbcore.Connect()) - to_chat(usr, "Failed to establish database connection.") + to_chat(usr, "Failed to establish database connection.", confidential = TRUE) return message_id = text2num(message_id) if(!message_id) return - var/editor_ckey = sanitizeSQL(usr.ckey) - var/editor_key = sanitizeSQL(usr.key) + var/editor_ckey = usr.ckey + var/editor_key = usr.key var/kn = key_name(usr) var/kna = key_name_admin(usr) - var/datum/DBQuery/query_find_edit_message = SSdbcore.NewQuery("SELECT type, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = targetckey), targetckey), IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = adminckey), targetckey), text FROM [format_table_name("messages")] WHERE id = [message_id] AND deleted = 0") + var/datum/db_query/query_find_edit_message = SSdbcore.NewQuery({" + SELECT + type, + IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = targetckey), targetckey), + IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = adminckey), targetckey), + text + FROM [format_table_name("messages")] + WHERE id = :id AND deleted = 0 + "}, list("id" = message_id)) if(!query_find_edit_message.warn_execute()) qdel(query_find_edit_message) return @@ -154,9 +184,12 @@ if(!new_text) qdel(query_find_edit_message) return - new_text = sanitizeSQL(new_text) - var/edit_text = sanitizeSQL("Edited by [editor_key] on [SQLtime()] from
[old_text]
to
[new_text]
") - var/datum/DBQuery/query_edit_message = SSdbcore.NewQuery("UPDATE [format_table_name("messages")] SET text = '[new_text]', lasteditor = '[editor_ckey]', edits = CONCAT(IFNULL(edits,''),'[edit_text]') WHERE id = [message_id] AND deleted = 0") + var/edit_text = "Edited by [editor_key] on [SQLtime()] from
[old_text]
to
[new_text]
" + var/datum/db_query/query_edit_message = SSdbcore.NewQuery({" + UPDATE [format_table_name("messages")] + SET text = :text, lasteditor = :lasteditor, edits = CONCAT(IFNULL(edits,''),:edit_text) + WHERE id = :id AND deleted = 0 + "}, list("text" = new_text, "lasteditor" = editor_ckey, "edit_text" = edit_text, "id" = message_id)) if(!query_edit_message.warn_execute()) qdel(query_edit_message) return @@ -171,16 +204,24 @@ /proc/edit_message_expiry(message_id, browse) if(!SSdbcore.Connect()) - to_chat(usr, "Failed to establish database connection.") + to_chat(usr, "Failed to establish database connection.", confidential = TRUE) return message_id = text2num(message_id) if(!message_id) return - var/editor_ckey = sanitizeSQL(usr.ckey) - var/editor_key = sanitizeSQL(usr.key) + var/editor_ckey = usr.ckey + var/editor_key = usr.key var/kn = key_name(usr) var/kna = key_name_admin(usr) - var/datum/DBQuery/query_find_edit_expiry_message = SSdbcore.NewQuery("SELECT type, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = targetckey), targetckey), IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = adminckey), adminckey), expire_timestamp FROM [format_table_name("messages")] WHERE id = [message_id] AND deleted = 0") + var/datum/db_query/query_find_edit_expiry_message = SSdbcore.NewQuery({" + SELECT + type, + IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = targetckey), targetckey), + IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = adminckey), adminckey), + expire_timestamp + FROM [format_table_name("messages")] + WHERE id = :id AND deleted = 0 + "}, list("id" = message_id)) if(!query_find_edit_expiry_message.warn_execute()) qdel(query_find_edit_expiry_message) return @@ -197,8 +238,9 @@ if(expire_time == "-1") new_expiry = "non-expiring" else - expire_time = sanitizeSQL(expire_time) - var/datum/DBQuery/query_validate_expire_time_edit = SSdbcore.NewQuery("SELECT IF(STR_TO_DATE('[expire_time]','%Y-%c-%d %T') > NOW(), STR_TO_DATE('[expire_time]','%Y-%c-%d %T'), 0)") + var/datum/db_query/query_validate_expire_time_edit = SSdbcore.NewQuery({" + SELECT IF(STR_TO_DATE(:expire_time,'%Y-%c-%d %T') > NOW(), STR_TO_DATE(:expire_time,'%Y-%c-%d %T'), 0) + "}, list("expire_time" = expire_time)) if(!query_validate_expire_time_edit.warn_execute()) qdel(query_validate_expire_time_edit) qdel(query_find_edit_expiry_message) @@ -206,14 +248,18 @@ if(query_validate_expire_time_edit.NextRow()) var/checktime = text2num(query_validate_expire_time_edit.item[1]) if(!checktime) - to_chat(usr, "Datetime entered is improperly formatted or not later than current server time.") + to_chat(usr, "Datetime entered is improperly formatted or not later than current server time.", confidential = TRUE) qdel(query_validate_expire_time_edit) qdel(query_find_edit_expiry_message) return new_expiry = query_validate_expire_time_edit.item[1] qdel(query_validate_expire_time_edit) - var/edit_text = sanitizeSQL("Expiration time edited by [editor_key] on [SQLtime()] from [old_expiry] to [new_expiry]
") - var/datum/DBQuery/query_edit_message_expiry = SSdbcore.NewQuery("UPDATE [format_table_name("messages")] SET expire_timestamp = [expire_time == "-1" ? "NULL" : "'[new_expiry]'"], lasteditor = '[editor_ckey]', edits = CONCAT(IFNULL(edits,''),'[edit_text]') WHERE id = [message_id] AND deleted = 0") + var/edit_text = "Expiration time edited by [editor_key] on [SQLtime()] from [old_expiry] to [new_expiry]
" + var/datum/db_query/query_edit_message_expiry = SSdbcore.NewQuery({" + UPDATE [format_table_name("messages")] + SET expire_timestamp = :expire_time, lasteditor = :lasteditor, edits = CONCAT(IFNULL(edits,''),:edit_text) + WHERE id = :id AND deleted = 0 + "}, list("expire_time" = (expire_time == "-1" ? null : new_expiry), "lasteditor" = editor_ckey, "edit_text" = edit_text, "id" = message_id)) if(!query_edit_message_expiry.warn_execute()) qdel(query_edit_message_expiry) qdel(query_find_edit_expiry_message) @@ -229,14 +275,22 @@ /proc/edit_message_severity(message_id) if(!SSdbcore.Connect()) - to_chat(usr, "Failed to establish database connection.") + to_chat(usr, "Failed to establish database connection.", confidential = TRUE) return message_id = text2num(message_id) if(!message_id) return var/kn = key_name(usr) var/kna = key_name_admin(usr) - var/datum/DBQuery/query_find_edit_note_severity = SSdbcore.NewQuery("SELECT type, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = targetckey), targetckey), IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = adminckey), adminckey), severity FROM [format_table_name("messages")] WHERE id = [message_id] AND deleted = 0") + var/datum/db_query/query_find_edit_note_severity = SSdbcore.NewQuery({" + SELECT + type, + IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = targetckey), targetckey), + IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = adminckey), adminckey), + severity + FROM [format_table_name("messages")] + WHERE id = :id AND deleted = 0 + "}, list("id" = message_id)) if(!query_find_edit_note_severity.warn_execute()) qdel(query_find_edit_note_severity) return @@ -247,15 +301,19 @@ var/old_severity = query_find_edit_note_severity.item[4] if(!old_severity) old_severity = "NA" - var/editor_key = sanitizeSQL(usr.key) - var/editor_ckey = sanitizeSQL(usr.ckey) + var/editor_key = usr.key + var/editor_ckey = usr.ckey var/new_severity = input("Set the severity of the note.", "Severity", null, null) as null|anything in list("high", "medium", "minor", "none") //lowercase for edit log consistency if(!new_severity) qdel(query_find_edit_note_severity) return - new_severity = sanitizeSQL(new_severity) - var/edit_text = sanitizeSQL("Note severity edited by [editor_key] on [SQLtime()] from [old_severity] to [new_severity]
") - var/datum/DBQuery/query_edit_note_severity = SSdbcore.NewQuery("UPDATE [format_table_name("messages")] SET severity = '[new_severity]', lasteditor = '[editor_ckey]', edits = CONCAT(IFNULL(edits,''),'[edit_text]') WHERE id = [message_id] AND deleted = 0") + new_severity = new_severity + var/edit_text = "Note severity edited by [editor_key] on [SQLtime()] from [old_severity] to [new_severity]
" + var/datum/db_query/query_edit_note_severity = SSdbcore.NewQuery({" + UPDATE [format_table_name("messages")] + SET severity = :severity, lasteditor = :lasteditor, edits = CONCAT(IFNULL(edits,''),:edit_text) + WHERE id = :id AND deleted = 0 + "}, list("severity" = new_severity, "lasteditor" = editor_ckey, "edit_text" = edit_text, "id" = message_id)) if(!query_edit_note_severity.warn_execute(async = TRUE)) qdel(query_edit_note_severity) qdel(qdel(query_find_edit_note_severity)) @@ -268,16 +326,24 @@ /proc/toggle_message_secrecy(message_id) if(!SSdbcore.Connect()) - to_chat(usr, "Failed to establish database connection.") + to_chat(usr, "Failed to establish database connection.", confidential = TRUE) return message_id = text2num(message_id) if(!message_id) return - var/editor_ckey = sanitizeSQL(usr.ckey) - var/editor_key = sanitizeSQL(usr.key) + var/editor_ckey = usr.ckey + var/editor_key = usr.key var/kn = key_name(usr) var/kna = key_name_admin(usr) - var/datum/DBQuery/query_find_message_secret = SSdbcore.NewQuery("SELECT type, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = targetckey), targetckey), IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = adminckey), targetckey), secret FROM [format_table_name("messages")] WHERE id = [message_id] AND deleted = 0") + var/datum/db_query/query_find_message_secret = SSdbcore.NewQuery({" + SELECT + type, + IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = targetckey), targetckey), + IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = adminckey), targetckey), + secret + FROM [format_table_name("messages")] + WHERE id = :id AND deleted = 0 + "}, list("id" = message_id)) if(!query_find_message_secret.warn_execute()) qdel(query_find_message_secret) return @@ -287,7 +353,11 @@ var/admin_key = query_find_message_secret.item[3] var/secret = text2num(query_find_message_secret.item[4]) var/edit_text = "Made [secret ? "not secret" : "secret"] by [editor_key] on [SQLtime()]
" - var/datum/DBQuery/query_message_secret = SSdbcore.NewQuery("UPDATE [format_table_name("messages")] SET secret = NOT secret, lasteditor = '[editor_ckey]', edits = CONCAT(IFNULL(edits,''),'[edit_text]') WHERE id = [message_id]") + var/datum/db_query/query_message_secret = SSdbcore.NewQuery({" + UPDATE [format_table_name("messages")] + SET secret = NOT secret, lasteditor = :lasteditor, edits = CONCAT(IFNULL(edits,''),:edit_text) + WHERE id = :id + "}, list("lasteditor" = editor_ckey, "edit_text" = edit_text, "id" = message_id)) if(!query_message_secret.warn_execute()) qdel(query_find_message_secret) qdel(query_message_secret) @@ -298,11 +368,9 @@ browse_messages(target_ckey = ckey(target_key), agegate = TRUE) qdel(query_find_message_secret) -/proc/browse_messages(type, target_ckey, index, linkless = FALSE, filter, agegate = FALSE, override = FALSE) - if((!override || IsAdminAdvancedProcCall()) && !check_rights(R_SENSITIVE)) - return +/proc/browse_messages(type, target_ckey, index, linkless = FALSE, filter, agegate = FALSE) if(!SSdbcore.Connect()) - to_chat(usr, "Failed to establish database connection.") + to_chat(usr, "Failed to establish database connection.", confidential = TRUE) return var/list/output = list() var/ruler = "
" @@ -329,7 +397,20 @@ else output += "Filter offline clients

" output += ruler - var/datum/DBQuery/query_get_type_messages = SSdbcore.NewQuery("SELECT id, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = targetckey), targetckey), targetckey, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = adminckey), adminckey), text, timestamp, server, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = lasteditor), lasteditor), expire_timestamp FROM [format_table_name("messages")] WHERE type = '[type]' AND deleted = 0 AND (expire_timestamp > NOW() OR expire_timestamp IS NULL)") + var/datum/db_query/query_get_type_messages = SSdbcore.NewQuery({" + SELECT + id, + IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = targetckey), targetckey), + targetckey, + IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = adminckey), adminckey), + text, + timestamp, + server, + IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = lasteditor), lasteditor), + expire_timestamp + FROM [format_table_name("messages")] + WHERE type = :type AND deleted = 0 AND (expire_timestamp > NOW() OR expire_timestamp IS NULL) + "}, list("type" = type)) if(!query_get_type_messages.warn_execute()) qdel(query_get_type_messages) return @@ -362,9 +443,24 @@ output += "
[text]
" qdel(query_get_type_messages) if(target_ckey) - target_ckey = sanitizeSQL(target_ckey) var/target_key - var/datum/DBQuery/query_get_messages = SSdbcore.NewQuery("SELECT type, secret, id, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = adminckey), adminckey), text, timestamp, server, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = lasteditor), lasteditor), DATEDIFF(NOW(), timestamp), IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = targetckey), targetckey), expire_timestamp, severity FROM [format_table_name("messages")] WHERE type <> 'memo' AND targetckey = '[target_ckey]' AND deleted = 0 AND (expire_timestamp > NOW() OR expire_timestamp IS NULL) ORDER BY timestamp DESC") + var/datum/db_query/query_get_messages = SSdbcore.NewQuery({" + SELECT + type, + secret, + id, + IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = adminckey), adminckey), + text, + timestamp, + server, + IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = lasteditor), lasteditor), + DATEDIFF(NOW(), timestamp), + IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = targetckey), targetckey), + expire_timestamp, severity + FROM [format_table_name("messages")] + WHERE type <> 'memo' AND targetckey = :targetckey AND deleted = 0 AND (expire_timestamp > NOW() OR expire_timestamp IS NULL) + ORDER BY timestamp DESC + "}, list("targetckey" = target_ckey)) if(!query_get_messages.warn_execute()) qdel(query_get_messages) return @@ -442,7 +538,9 @@ notedata += data qdel(query_get_messages) if(!target_key) - var/datum/DBQuery/query_get_message_key = SSdbcore.NewQuery("SELECT byond_key FROM [format_table_name("player")] WHERE ckey = '[target_ckey]'") + var/datum/db_query/query_get_message_key = SSdbcore.NewQuery({" + SELECT byond_key FROM [format_table_name("player")] WHERE ckey = :ckey + "}, list("ckey" = target_ckey)) if(!query_get_message_key.warn_execute()) qdel(query_get_message_key) return @@ -479,8 +577,6 @@ var/search output += "
Add messageAdd watchlist entryAdd note
" output += ruler - if(!isnum(index)) - index = sanitizeSQL(index) switch(index) if(1) search = "^." @@ -488,7 +584,17 @@ search = "^\[^\[:alpha:\]\]" else search = "^[index]" - var/datum/DBQuery/query_list_messages = SSdbcore.NewQuery("SELECT DISTINCT targetckey, (SELECT byond_key FROM [format_table_name("player")] WHERE ckey = targetckey) FROM [format_table_name("messages")] WHERE type <> 'memo' AND targetckey REGEXP '[search]' AND deleted = 0 AND (expire_timestamp > NOW() OR expire_timestamp IS NULL) ORDER BY targetckey") + var/datum/db_query/query_list_messages = SSdbcore.NewQuery({" + SELECT DISTINCT + targetckey, + (SELECT byond_key FROM [format_table_name("player")] WHERE ckey = targetckey) + FROM [format_table_name("messages")] + WHERE type <> 'memo' + AND targetckey REGEXP :search + AND deleted = 0 + AND (expire_timestamp > NOW() OR expire_timestamp IS NULL) + ORDER BY targetckey + "}, list("search" = search)) if(!query_list_messages.warn_execute()) qdel(query_list_messages) return @@ -512,17 +618,24 @@ /proc/get_message_output(type, target_ckey) if(!SSdbcore.Connect()) - to_chat(usr, "Failed to establish database connection.") + to_chat(usr, "Failed to establish database connection.", confidential = TRUE) return if(!type) return var/output - if(target_ckey) - target_ckey = sanitizeSQL(target_ckey) - var/query = "SELECT id, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = adminckey), adminckey), text, timestamp, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = lasteditor), lasteditor) FROM [format_table_name("messages")] WHERE type = '[type]' AND deleted = 0 AND (expire_timestamp > NOW() OR expire_timestamp IS NULL)" - if(type == "message" || type == "watchlist entry") - query += " AND targetckey = '[target_ckey]'" - var/datum/DBQuery/query_get_message_output = SSdbcore.NewQuery(query) + var/datum/db_query/query_get_message_output = SSdbcore.NewQuery({" + SELECT + id, + IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = adminckey), adminckey), + text, + timestamp, + IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = lasteditor), lasteditor) + FROM [format_table_name("messages")] + WHERE type = :type + AND deleted = 0 + AND (expire_timestamp > NOW() OR expire_timestamp IS NULL) + AND ((type != 'message' AND type != 'watchlist entry') OR targetckey = :targetckey) + "}, list("targetckey" = target_ckey, "type" = type)) if(!query_get_message_output.warn_execute()) qdel(query_get_message_output) return @@ -536,7 +649,10 @@ if("message") output += "Admin message left by [admin_key] on [timestamp]" output += "
[text]
" - var/datum/DBQuery/query_message_read = SSdbcore.NewQuery("UPDATE [format_table_name("messages")] SET type = 'message sent' WHERE id = [message_id]") + var/datum/db_query/query_message_read = SSdbcore.NewQuery( + "UPDATE [format_table_name("messages")] SET type = 'message sent' WHERE id = :id", + list("id" = message_id) + ) if(!query_message_read.warn_execute()) qdel(query_get_message_output) qdel(query_message_read) @@ -544,7 +660,7 @@ qdel(query_message_read) if("watchlist entry") message_admins("Notice: [key_name_admin(target_ckey)] has been on the watchlist since [timestamp] and has just connected - Reason: [text]") - send2irc_adminless_only("Watchlist", "[key_name(target_ckey)] is on the watchlist and has just connected - Reason: [text]") + send2tgs_adminless_only("Watchlist", "[key_name(target_ckey)] is on the watchlist and has just connected - Reason: [text]") if("memo") output += "Memo by [admin_key] on [timestamp]" if(editor_key) @@ -576,7 +692,7 @@ var/timestamp = note.group[1] notetext = note.group[2] var/admin_ckey = note.group[3] - var/datum/DBQuery/query_convert_time = SSdbcore.NewQuery("SELECT ADDTIME(STR_TO_DATE('[timestamp]','%d-%b-%Y'), '0')") + var/datum/db_query/query_convert_time = SSdbcore.NewQuery("SELECT ADDTIME(STR_TO_DATE(:timestamp,'%d-%b-%Y'), '0')", list("timestamp" = timestamp)) if(!query_convert_time.Execute()) qdel(query_convert_time) return @@ -591,7 +707,7 @@ /*alternatively this proc can be run once to pass through every note and attempt to convert it before deleting the file, if done then AUTOCONVERT_NOTES should be turned off this proc can take several minutes to execute fully if converting and cause DD to hang if converting a lot of notes; it's not advised to do so while a server is live /proc/mass_convert_notes() - to_chat(world, "Beginning mass note conversion") + to_chat(world, "Beginning mass note conversion", confidential = TRUE) var/savefile/notesfile = new(NOTESFILE) if(!notesfile) log_game("Error: Cannot access [NOTESFILE]") @@ -599,7 +715,7 @@ this proc can take several minutes to execute fully if converting and cause DD t notesfile.cd = "/" for(var/ckey in notesfile.dir) convert_notes_sql(ckey) - to_chat(world, "Deleting NOTESFILE") + to_chat(world, "Deleting NOTESFILE", confidential = TRUE) fdel(NOTESFILE) - to_chat(world, "Finished mass note conversion, remember to turn off AUTOCONVERT_NOTES")*/ + to_chat(world, "Finished mass note conversion, remember to turn off AUTOCONVERT_NOTES", confidential = TRUE)*/ #undef NOTESFILE diff --git a/code/modules/admin/stickyban.dm b/code/modules/admin/stickyban.dm index ef0bfe8e70..57487f3144 100644 --- a/code/modules/admin/stickyban.dm +++ b/code/modules/admin/stickyban.dm @@ -32,15 +32,11 @@ return ban["message"] = "[reason]" - if(SSdbcore.Connect()) // todo: second wave - // var/datum/db_query/query_create_stickyban = SSdbcore.NewQuery({" - // INSERT INTO [format_table_name("stickyban")] (ckey, reason, banning_admin) - // VALUES (:ckey, :message, :banning_admin) - // "}, list("ckey" = ckey, "message" = ban["message"], "banning_admin" = usr.ckey)) - var/datum/DBQuery/query_create_stickyban = SSdbcore.NewQuery({" + if(SSdbcore.Connect()) + var/datum/db_query/query_create_stickyban = SSdbcore.NewQuery({" INSERT INTO [format_table_name("stickyban")] (ckey, reason, banning_admin) - VALUES ([ckey], [ban["message"]], [usr.ckey]) - "}) + VALUES (:ckey, :message, :banning_admin) + "}, list("ckey" = ckey, "message" = ban["message"], "banning_admin" = usr.ckey)) if (query_create_stickyban.warn_execute()) ban["fromdb"] = TRUE qdel(query_create_stickyban) @@ -74,19 +70,14 @@ SSstickyban.cache -= ckey if (SSdbcore.Connect()) - // SSdbcore.QuerySelect(list( - // SSdbcore.NewQuery("DELETE FROM [format_table_name("stickyban")] WHERE ckey = :ckey", list("ckey" = ckey)), - // SSdbcore.NewQuery("DELETE FROM [format_table_name("stickyban_matched_ckey")] WHERE stickyban = :ckey", list("ckey" = ckey)), - // SSdbcore.NewQuery("DELETE FROM [format_table_name("stickyban_matched_cid")] WHERE stickyban = :ckey", list("ckey" = ckey)), - // SSdbcore.NewQuery("DELETE FROM [format_table_name("stickyban_matched_ip")] WHERE stickyban = :ckey", list("ckey" = ckey)) - // ), warn = TRUE, qdel = TRUE) SSdbcore.QuerySelect(list( - SSdbcore.NewQuery("DELETE FROM [format_table_name("stickyban")] WHERE ckey = [ckey]"), - SSdbcore.NewQuery("DELETE FROM [format_table_name("stickyban_matched_ckey")] WHERE stickyban = [ckey]"), - SSdbcore.NewQuery("DELETE FROM [format_table_name("stickyban_matched_cid")] WHERE stickyban = [ckey]"), - SSdbcore.NewQuery("DELETE FROM [format_table_name("stickyban_matched_ip")] WHERE stickyban = [ckey]") + SSdbcore.NewQuery("DELETE FROM [format_table_name("stickyban")] WHERE ckey = :ckey", list("ckey" = ckey)), + SSdbcore.NewQuery("DELETE FROM [format_table_name("stickyban_matched_ckey")] WHERE stickyban = :ckey", list("ckey" = ckey)), + SSdbcore.NewQuery("DELETE FROM [format_table_name("stickyban_matched_cid")] WHERE stickyban = :ckey", list("ckey" = ckey)), + SSdbcore.NewQuery("DELETE FROM [format_table_name("stickyban_matched_ip")] WHERE stickyban = :ckey", list("ckey" = ckey)) ), warn = TRUE, qdel = TRUE) + log_admin_private("[key_name(usr)] removed [ckey]'s stickyban") message_admins("[key_name_admin(usr)] removed [ckey]'s stickyban") @@ -128,12 +119,9 @@ SSstickyban.cache[ckey] = ban if (SSdbcore.Connect()) - // var/datum/db_query/query_remove_stickyban_alt = SSdbcore.NewQuery( - // "DELETE FROM [format_table_name("stickyban_matched_ckey")] WHERE stickyban = :ckey AND matched_ckey = :alt", - // list("ckey" = ckey, "alt" = alt) - // ) - var/datum/DBQuery/query_remove_stickyban_alt = SSdbcore.NewQuery( - "DELETE FROM [format_table_name("stickyban_matched_ckey")] WHERE stickyban = [ckey] AND matched_ckey = [alt]" + var/datum/db_query/query_remove_stickyban_alt = SSdbcore.NewQuery( + "DELETE FROM [format_table_name("stickyban_matched_ckey")] WHERE stickyban = :ckey AND matched_ckey = :alt", + list("ckey" = ckey, "alt" = alt) ) query_remove_stickyban_alt.warn_execute() qdel(query_remove_stickyban_alt) @@ -165,12 +153,9 @@ SSstickyban.cache[ckey] = ban if (SSdbcore.Connect()) - // var/datum/db_query/query_edit_stickyban = SSdbcore.NewQuery( - // "UPDATE [format_table_name("stickyban")] SET reason = :reason WHERE ckey = :ckey", - // list("reason" = reason, "ckey" = ckey) - // ) - var/datum/DBQuery/query_edit_stickyban = SSdbcore.NewQuery( - "UPDATE [format_table_name("stickyban")] SET reason = [reason] WHERE ckey = [ckey]" + var/datum/db_query/query_edit_stickyban = SSdbcore.NewQuery( + "UPDATE [format_table_name("stickyban")] SET reason = :reason WHERE ckey = :ckey", + list("reason" = reason, "ckey" = ckey) ) query_edit_stickyban.warn_execute() qdel(query_edit_stickyban) @@ -218,12 +203,9 @@ SSstickyban.cache[ckey] = ban if (SSdbcore.Connect()) - // var/datum/db_query/query_exempt_stickyban_alt = SSdbcore.NewQuery( - // "UPDATE [format_table_name("stickyban_matched_ckey")] SET exempt = 1 WHERE stickyban = :ckey AND matched_ckey = :alt", - // list("ckey" = ckey, "alt" = alt) - // ) - var/datum/DBQuery/query_exempt_stickyban_alt = SSdbcore.NewQuery( - "UPDATE [format_table_name("stickyban_matched_ckey")] SET exempt = 1 WHERE stickyban = [ckey] AND matched_ckey = [alt]" + var/datum/db_query/query_exempt_stickyban_alt = SSdbcore.NewQuery( + "UPDATE [format_table_name("stickyban_matched_ckey")] SET exempt = 1 WHERE stickyban = :ckey AND matched_ckey = :alt", + list("ckey" = ckey, "alt" = alt) ) query_exempt_stickyban_alt.warn_execute() qdel(query_exempt_stickyban_alt) @@ -271,12 +253,9 @@ SSstickyban.cache[ckey] = ban if (SSdbcore.Connect()) - // var/datum/db_query/query_unexempt_stickyban_alt = SSdbcore.NewQuery( - // "UPDATE [format_table_name("stickyban_matched_ckey")] SET exempt = 0 WHERE stickyban = :ckey AND matched_ckey = :alt", - // list("ckey" = ckey, "alt" = alt) - // ) - var/datum/DBQuery/query_unexempt_stickyban_alt = SSdbcore.NewQuery( - "UPDATE [format_table_name("stickyban_matched_ckey")] SET exempt = 0 WHERE stickyban = [ckey] AND matched_ckey = [alt]" + var/datum/db_query/query_unexempt_stickyban_alt = SSdbcore.NewQuery( + "UPDATE [format_table_name("stickyban_matched_ckey")] SET exempt = 0 WHERE stickyban = :ckey AND matched_ckey = :alt", + list("ckey" = ckey, "alt" = alt) ) query_unexempt_stickyban_alt.warn_execute() qdel(query_unexempt_stickyban_alt) diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm index 86ddc24ca3..cd29da186c 100644 --- a/code/modules/admin/topic.dm +++ b/code/modules/admin/topic.dm @@ -1269,8 +1269,10 @@ else if(href_list["messageedits"]) if(!check_rights(R_ADMIN)) return - var/message_id = sanitizeSQL("[href_list["messageedits"]]") - var/datum/DBQuery/query_get_message_edits = SSdbcore.NewQuery("SELECT edits FROM [format_table_name("messages")] WHERE id = '[message_id]'") + var/datum/db_query/query_get_message_edits = SSdbcore.NewQuery( + "SELECT edits FROM [format_table_name("messages")] WHERE id = :message_id", + list("message_id" = href_list["messageedits"]) + ) if(!query_get_message_edits.warn_execute()) qdel(query_get_message_edits) return @@ -2499,9 +2501,6 @@ break return - else if(href_list["secrets"]) - Secrets_topic(href_list["secrets"],href_list) - else if(href_list["ac_view_wanted"]) //Admin newscaster Topic() stuff be here if(!check_rights(R_ADMIN)) return @@ -2970,16 +2969,19 @@ to_chat(usr, "The client chosen is an admin! Cannot mentorize.") return if(SSdbcore.Connect()) - var/datum/DBQuery/query_get_mentor = SSdbcore.NewQuery("SELECT id FROM [format_table_name("mentor")] WHERE ckey = '[ckey]'") + var/datum/db_query/query_get_mentor = SSdbcore.NewQuery( + "SELECT id FROM [format_table_name("mentor")] WHERE ckey = :ckey", + list("ckey" = ckey) + ) if(!query_get_mentor.warn_execute()) return if(query_get_mentor.NextRow()) to_chat(usr, "[ckey] is already a mentor.") return - var/datum/DBQuery/query_add_mentor = SSdbcore.NewQuery("INSERT INTO `[format_table_name("mentor")]` (`id`, `ckey`) VALUES (null, '[ckey]')") + var/datum/db_query/query_add_mentor = SSdbcore.NewQuery("INSERT INTO `[format_table_name("mentor")]` (`id`, `ckey`) VALUES (null, '[ckey]')") if(!query_add_mentor.warn_execute()) return - var/datum/DBQuery/query_add_admin_log = SSdbcore.NewQuery("INSERT INTO `[format_table_name("admin_log")]` (`id` ,`datetime` ,`adminckey` ,`adminip` ,`log` ) VALUES (NULL , NOW( ) , '[usr.ckey]', '[usr.client.address]', 'Added new mentor [ckey]');") + var/datum/db_query/query_add_admin_log = SSdbcore.NewQuery("INSERT INTO `[format_table_name("admin_log")]` (`id` ,`datetime` ,`adminckey` ,`adminip` ,`log` ) VALUES (NULL , NOW( ) , '[usr.ckey]', '[usr.client.address]', 'Added new mentor [ckey]');") if(!query_add_admin_log.warn_execute()) return else @@ -3003,10 +3005,10 @@ C.mentor_datum = null GLOB.mentors -= C if(SSdbcore.Connect()) - var/datum/DBQuery/query_remove_mentor = SSdbcore.NewQuery("DELETE FROM [format_table_name("mentor")] WHERE ckey = '[ckey]'") + var/datum/db_query/query_remove_mentor = SSdbcore.NewQuery("DELETE FROM [format_table_name("mentor")] WHERE ckey = '[ckey]'") if(!query_remove_mentor.warn_execute()) return - var/datum/DBQuery/query_add_admin_log = SSdbcore.NewQuery("INSERT INTO `[format_table_name("admin_log")]` (`id` ,`datetime` ,`adminckey` ,`adminip` ,`log` ) VALUES (NULL , NOW( ) , '[usr.ckey]', '[usr.client.address]', 'Removed mentor [ckey]');") + var/datum/db_query/query_add_admin_log = SSdbcore.NewQuery("INSERT INTO `[format_table_name("admin_log")]` (`id` ,`datetime` ,`adminckey` ,`adminip` ,`log` ) VALUES (NULL , NOW( ) , '[usr.ckey]', '[usr.client.address]', 'Removed mentor [ckey]');") if(!query_add_admin_log.warn_execute()) return else diff --git a/code/modules/admin/verbs/adminhelp.dm b/code/modules/admin/verbs/adminhelp.dm index 45d05c70b9..5c35450592 100644 --- a/code/modules/admin/verbs/adminhelp.dm +++ b/code/modules/admin/verbs/adminhelp.dm @@ -205,7 +205,7 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new) MessageNoRecipient(msg) //send it to irc if nobody is on and tell us how many were on - var/admin_number_present = send2irc_adminless_only(initiator_ckey, "Ticket #[id]: [name]") + var/admin_number_present = send2tgs_adminless_only(initiator_ckey, "Ticket #[id]: [name]") log_admin_private("Ticket #[id]: [key_name(initiator)]: [name] - heard by [admin_number_present] non-AFK admins who have +BAN.") if(admin_number_present <= 0) to_chat(C, "No active admins are online, your adminhelp was sent to the admin irc.") @@ -222,7 +222,7 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new) /datum/admin_help/proc/AddInteraction(formatted_message) if(heard_by_no_admins && usr && usr.ckey != initiator_ckey) heard_by_no_admins = FALSE - send2irc(initiator_ckey, "Ticket #[id]: Answered by [key_name(usr)]") + send2adminchat(initiator_ckey, "Ticket #[id]: Answered by [key_name(usr)]") _interactions += "[TIME_STAMP("hh:mm:ss", FALSE)]: [formatted_message]" //Removes the ahelp verb and returns it after 2 minutes @@ -573,7 +573,7 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new) . = list("total" = list(), "noflags" = list(), "afk" = list(), "stealth" = list(), "present" = list()) for(var/client/X in GLOB.admins) .["total"] += X - if(requiredflags != 0 && !check_rights_for(X, requiredflags)) + if(requiredflags != NONE && !check_rights_for(X, requiredflags)) .["noflags"] += X else if(X.is_afk()) .["afk"] += X @@ -582,7 +582,7 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new) else .["present"] += X -/proc/send2irc_adminless_only(source, msg, requiredflags = R_BAN) +/proc/send2tgs_adminless_only(source, msg, requiredflags = R_BAN) var/list/adm = get_admin_counts(requiredflags) var/list/activemins = adm["present"] . = activemins.len @@ -596,30 +596,52 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new) final = "[msg] - No admins online" else final = "[msg] - All admins stealthed\[[english_list(stealthmins)]\], AFK\[[english_list(afkmins)]\], or lacks +BAN\[[english_list(powerlessmins)]\]! Total: [allmins.len] " - send2irc(source,final) + send2adminchat(source,final) send2otherserver(source,final) - -/proc/send2irc(msg,msg2) - msg = replacetext(replacetext(msg, "\proper", ""), "\improper", "") - msg2 = replacetext(replacetext(msg2, "\proper", ""), "\improper", "") - world.TgsTargetedChatBroadcast("[msg] | [msg2]", TRUE) - -/proc/send2otherserver(source,msg,type = "Ahelp") - var/comms_key = CONFIG_GET(string/comms_key) - if(!comms_key) +/** + * Sends a message to a set of cross-communications-enabled servers using world topic calls + * + * Arguments: + * * source - Who sent this message + * * msg - The message body + * * type - The type of message, becomes the topic command under the hood + * * target_servers - A collection of servers to send the message to, defined in config + * * additional_data - An (optional) associated list of extra parameters and data to send with this world topic call + */ +/proc/send2otherserver(source, msg, type = "Ahelp", target_servers, list/additional_data = list()) + if(!CONFIG_GET(string/comms_key)) + debug_world_log("Server cross-comms message not sent for lack of configured key") return - var/list/message = list() - message["message_sender"] = source - message["message"] = msg - message["source"] = "([CONFIG_GET(string/cross_comms_name)])" - message["key"] = comms_key - message += type + + var/our_id = CONFIG_GET(string/cross_comms_name) + additional_data["message_sender"] = source + additional_data["message"] = msg + additional_data["source"] = "([our_id])" + additional_data += type var/list/servers = CONFIG_GET(keyed_list/cross_server) for(var/I in servers) - world.Export("[servers[I]]?[list2params(message)]") + if(I == our_id) //No sending to ourselves + continue + if(target_servers && !(I in target_servers)) + continue + world.send_cross_comms(I, additional_data) +/// Sends a message to a given cross comms server by name (by name for security). +/world/proc/send_cross_comms(server_name, list/message, auth = TRUE) + set waitfor = FALSE + if (auth) + var/comms_key = CONFIG_GET(string/comms_key) + if(!comms_key) + debug_world_log("Server cross-comms message not sent for lack of configured key") + return + message["key"] = comms_key + var/list/servers = CONFIG_GET(keyed_list/cross_server) + var/server_url = servers[server_name] + if (!server_url) + CRASH("Invalid cross comms config: [server_name]") + world.Export("[server_url]?[list2params(message)]") /proc/ircadminwho() var/list/message = list("Admins: ") diff --git a/code/modules/admin/verbs/adminpm.dm b/code/modules/admin/verbs/adminpm.dm index ab0b0d933a..3a708ef182 100644 --- a/code/modules/admin/verbs/adminpm.dm +++ b/code/modules/admin/verbs/adminpm.dm @@ -165,7 +165,7 @@ to_chat(src, "PM to-Admins: [rawmsg]", confidential = TRUE) var/datum/admin_help/AH = admin_ticket_log(src, "Reply PM from-[key_name(src, TRUE, TRUE)] to External: [keywordparsedmsg]") ircreplyamount-- - send2irc("[AH ? "#[AH.id] " : ""]Reply: [ckey]", rawmsg) + send2adminchat("[AH ? "#[AH.id] " : ""]Reply: [ckey]", rawmsg) else var/badmin = FALSE //Lets figure out if an admin is getting bwoinked. diff --git a/code/modules/admin/verbs/debug.dm b/code/modules/admin/verbs/debug.dm index 5704448053..29ee35c117 100644 --- a/code/modules/admin/verbs/debug.dm +++ b/code/modules/admin/verbs/debug.dm @@ -790,11 +790,12 @@ if(!check_rights(R_DEBUG)) return - SSmedals.hub_enabled = !SSmedals.hub_enabled + SSachievements.achievements_enabled = !SSachievements.achievements_enabled - message_admins("[key_name_admin(src)] [SSmedals.hub_enabled ? "disabled" : "enabled"] the medal hub lockout.") + message_admins("[key_name_admin(src)] [SSachievements.achievements_enabled ? "disabled" : "enabled"] the medal hub lockout.") SSblackbox.record_feedback("tally", "admin_verb", 1, "Toggle Medal Disable") // If... - log_admin("[key_name(src)] [SSmedals.hub_enabled ? "disabled" : "enabled"] the medal hub lockout.") + log_admin("[key_name(src)] [SSachievements.achievements_enabled ? "disabled" : "enabled"] the medal hub lockout.") + /client/proc/view_runtimes() set category = "Debug" diff --git a/code/modules/admin/verbs/diagnostics.dm b/code/modules/admin/verbs/diagnostics.dm index 6f8e9e703f..358e7f1bec 100644 --- a/code/modules/admin/verbs/diagnostics.dm +++ b/code/modules/admin/verbs/diagnostics.dm @@ -63,3 +63,31 @@ load_admins() SSblackbox.record_feedback("tally", "admin_verb", 1, "Reload All Admins") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! message_admins("[key_name_admin(usr)] manually reloaded admins") + +/client/proc/toggle_cdn() + set name = "Toggle CDN" + set category = "Server" + var/static/admin_disabled_cdn_transport = null + if (alert(usr, "Are you sure you want to toggle the CDN asset transport?", "Confirm", "Yes", "No") != "Yes") + return + var/current_transport = CONFIG_GET(string/asset_transport) + if (!current_transport || current_transport == "simple") + if (admin_disabled_cdn_transport) + CONFIG_SET(string/asset_transport, admin_disabled_cdn_transport) + admin_disabled_cdn_transport = null + SSassets.OnConfigLoad() + message_admins("[key_name_admin(usr)] re-enabled the CDN asset transport") + log_admin("[key_name(usr)] re-enabled the CDN asset transport") + else + to_chat(usr, "The CDN is not enabled!") + if (alert(usr, "The CDN asset transport is not enabled! If you having issues with assets you can also try disabling filename mutations.", "The CDN asset transport is not enabled!", "Try disabling filename mutations", "Nevermind") == "Try disabling filename mutations") + SSassets.transport.dont_mutate_filenames = !SSassets.transport.dont_mutate_filenames + message_admins("[key_name_admin(usr)] [(SSassets.transport.dont_mutate_filenames ? "disabled" : "re-enabled")] asset filename transforms") + log_admin("[key_name(usr)] [(SSassets.transport.dont_mutate_filenames ? "disabled" : "re-enabled")] asset filename transforms") + else + admin_disabled_cdn_transport = current_transport + CONFIG_SET(string/asset_transport, "simple") + SSassets.OnConfigLoad() + SSassets.transport.dont_mutate_filenames = TRUE + message_admins("[key_name_admin(usr)] disabled the CDN asset transport") + log_admin("[key_name(usr)] disabled the CDN asset transport") diff --git a/code/modules/admin/verbs/panicbunker.dm b/code/modules/admin/verbs/panicbunker.dm index daaf15c70f..92bc840c5a 100644 --- a/code/modules/admin/verbs/panicbunker.dm +++ b/code/modules/admin/verbs/panicbunker.dm @@ -13,7 +13,7 @@ if (new_pb && !SSdbcore.Connect()) message_admins("The Database is not connected! Panic bunker will not work until the connection is reestablished.") SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Panic Bunker", "[new_pb ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - send2irc("Panic Bunker", "[key_name(usr)] has toggled the Panic Bunker, it is now [new_pb ? "enabled" : "disabled"].") + send2adminchat("Panic Bunker", "[key_name(usr)] has toggled the Panic Bunker, it is now [new_pb ? "enabled" : "disabled"].") /client/proc/addbunkerbypass(ckeytobypass as text) set category = "Special Verbs" @@ -28,7 +28,7 @@ SSpersistence.SavePanicBunker() //we can do this every time, it's okay log_admin("[key_name(usr)] has added [ckeytobypass] to the current round's bunker bypass list.") message_admins("[key_name_admin(usr)] has added [ckeytobypass] to the current round's bunker bypass list.") - send2irc("Panic Bunker", "[key_name(usr)] has added [ckeytobypass] to the current round's bunker bypass list.") + send2adminchat("Panic Bunker", "[key_name(usr)] has added [ckeytobypass] to the current round's bunker bypass list.") /client/proc/revokebunkerbypass(ckeytobypass as text) set category = "Special Verbs" @@ -42,4 +42,4 @@ SSpersistence.SavePanicBunker() log_admin("[key_name(usr)] has removed [ckeytobypass] from the current round's bunker bypass list.") message_admins("[key_name_admin(usr)] has removed [ckeytobypass] from the current round's bunker bypass list.") - send2irc("Panic Bunker", "[key_name(usr)] has removed [ckeytobypass] from the current round's bunker bypass list.") + send2adminchat("Panic Bunker", "[key_name(usr)] has removed [ckeytobypass] from the current round's bunker bypass list.") diff --git a/code/modules/admin/verbs/pray.dm b/code/modules/admin/verbs/pray.dm index e532dcc2b2..873007a2a5 100644 --- a/code/modules/admin/verbs/pray.dm +++ b/code/modules/admin/verbs/pray.dm @@ -3,7 +3,7 @@ set name = "Pray" if(GLOB.say_disabled) //This is here to try to identify lag problems - to_chat(usr, "Speech is currently admin-disabled.") + to_chat(usr, "Speech is currently admin-disabled.", confidential = TRUE) return msg = copytext_char(sanitize(msg), 1, MAX_MESSAGE_LEN) @@ -12,7 +12,7 @@ log_prayer("[src.key]/([src.name]): [msg]") if(usr.client) if(usr.client.prefs.muted & MUTE_PRAY) - to_chat(usr, "You cannot pray (muted).") + to_chat(usr, "You cannot pray (muted).", confidential = TRUE) return if(src.client.handle_spam_prevention(msg,MUTE_PRAY)) return @@ -44,34 +44,35 @@ for(var/client/C in GLOB.admins) if(C.prefs.chat_toggles & CHAT_PRAYER) - to_chat(C, msg) + to_chat(C, msg, confidential = TRUE) if(C.prefs.toggles & SOUND_PRAYERS) if(usr.job == "Chaplain") SEND_SOUND(C, sound('sound/effects/pray.ogg')) - else - SEND_SOUND(C, sound('sound/effects/ding.ogg')) - to_chat(usr, "You pray to the gods: \"[msg_tmp]\"") + to_chat(usr, "You pray to the gods: \"[msg_tmp]\"", confidential = TRUE) SSblackbox.record_feedback("tally", "admin_verb", 1, "Prayer") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! //log_admin("HELP: [key_name(src)]: [msg]") -/proc/CentCom_announce(text , mob/Sender) +/// Used by communications consoles to message CentCom +/proc/message_centcom(text, mob/sender) var/msg = copytext_char(sanitize(text), 1, MAX_MESSAGE_LEN) - msg = "CENTCOM:[ADMIN_FULLMONTY(Sender)] [ADMIN_CENTCOM_REPLY(Sender)]: [msg]" - to_chat(GLOB.admins, msg) - for(var/obj/machinery/computer/communications/C in GLOB.machines) - C.overrideCooldown() + msg = "CENTCOM:[ADMIN_FULLMONTY(sender)] [ADMIN_CENTCOM_REPLY(sender)]: [msg]" + to_chat(GLOB.admins, msg, confidential = TRUE) + for(var/obj/machinery/computer/communications/console in GLOB.machines) + console.override_cooldown() -/proc/Syndicate_announce(text , mob/Sender) +/// Used by communications consoles to message the Syndicate +/proc/message_syndicate(text, mob/sender) var/msg = copytext_char(sanitize(text), 1, MAX_MESSAGE_LEN) - msg = "SYNDICATE:[ADMIN_FULLMONTY(Sender)] [ADMIN_SYNDICATE_REPLY(Sender)]: [msg]" - to_chat(GLOB.admins, msg) - for(var/obj/machinery/computer/communications/C in GLOB.machines) - C.overrideCooldown() + msg = "SYNDICATE:[ADMIN_FULLMONTY(sender)] [ADMIN_SYNDICATE_REPLY(sender)]: [msg]" + to_chat(GLOB.admins, msg, confidential = TRUE) + for(var/obj/machinery/computer/communications/console in GLOB.machines) + console.override_cooldown() -/proc/Nuke_request(text , mob/Sender) +/// Used by communications consoles to request the nuclear launch codes +/proc/nuke_request(text, mob/sender) var/msg = copytext_char(sanitize(text), 1, MAX_MESSAGE_LEN) - msg = "NUKE CODE REQUEST:[ADMIN_FULLMONTY(Sender)] [ADMIN_CENTCOM_REPLY(Sender)] [ADMIN_SET_SD_CODE]: [msg]" - to_chat(GLOB.admins, msg) - for(var/obj/machinery/computer/communications/C in GLOB.machines) - C.overrideCooldown() + msg = "NUKE CODE REQUEST:[ADMIN_FULLMONTY(sender)] [ADMIN_CENTCOM_REPLY(sender)] [ADMIN_SET_SD_CODE]: [msg]" + to_chat(GLOB.admins, msg, confidential = TRUE) + for(var/obj/machinery/computer/communications/console in GLOB.machines) + console.override_cooldown() diff --git a/code/modules/admin/verbs/randomverbs.dm b/code/modules/admin/verbs/randomverbs.dm index 84702898ec..e5390bc457 100644 --- a/code/modules/admin/verbs/randomverbs.dm +++ b/code/modules/admin/verbs/randomverbs.dm @@ -1547,22 +1547,17 @@ GLOBAL_LIST_EMPTY(custom_outfits) //Admin created outfits msg += "" 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, " 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) @@ -162,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/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/clockwork_slab.dm b/code/modules/antagonists/clockcult/clock_items/clockwork_slab.dm index ff6dc12ce1..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 @@ -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/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/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 43678e17c6..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 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 22fb266033..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 @@ -47,9 +47,9 @@ "smmon_3.gif" = 'icons/program_icons/smmon_3.gif', "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', - // "robotact.gif" = 'icons/program_icons/robotact.gif' + "smmon_6.gif" = 'icons/program_icons/smmon_6.gif', + "borg_mon.gif" = 'icons/program_icons/borg_mon.gif', + "robotact.gif" = 'icons/program_icons/robotact.gif' ) /datum/asset/simple/radar_assets @@ -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,32 +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" @@ -317,28 +308,28 @@ "pill21" = 'icons/UI_Icons/Pills/pill21.png', "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', - ) -*/ + +// /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)) @@ -485,7 +476,7 @@ /datum/asset/simple/orbit assets = list( - "ghost.png" = 'html/ghost.png' + "ghost.png" = 'html/ghost.png' ) /datum/asset/simple/vv @@ -521,7 +512,8 @@ var/list/portrait = p var/png = "data/paintings/[tab]/[portrait["md5"]].png" if(fexists(png)) - assets[portrait["title"]] = 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 @@ -537,3 +529,27 @@ 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/asset_cache/readme.md b/code/modules/asset_cache/readme.md index c8c9d78b71..82e6bea896 100644 --- a/code/modules/asset_cache/readme.md +++ b/code/modules/asset_cache/readme.md @@ -24,7 +24,7 @@ Call .get_url_mappings() to get an associated list with the urls your assets can See the documentation for `/datum/asset_transport` for the backend api the asset datums utilize. -The global variable `SSassets.transport` contains the currently configured transport. +The global variable `SSassets.transport` contains the currently configured transport. @@ -32,6 +32,6 @@ The global variable `SSassets.transport` contains the currently configured trans Because byond browse() calls use non-blocking queues, if your code uses output() (which bypasses all of these queues) to invoke javascript functions you will need to first have the javascript announce to the server it has loaded before trying to invoke js functions. -To make your code work with any CDNs configured by the server, you must make sure assets are referenced from the url returned by `get_url_mappings()` or by asset_transport's `get_asset_url()`. (TGUI also has helpers for this.) If this can not be easily done, you can bypass the cdn using legacy assets, see the simple asset datum for details. +To make your code work with any CDNs configured by the server, you must make sure assets are referenced from the url returned by `get_url_mappings()` or by asset_transport's `get_asset_url()`. (TGUI also has helpers for this.) If this can not be easily done, you can bypass the cdn using legacy assets, see the simple asset datum for details. CSS files that use url() can be made to use the CDN without needing to rewrite all url() calls in code by using the namespaced helper datum. See the documentation for `/datum/asset/simple/namespaced` for details. 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/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/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 2690ad555b..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) @@ -266,14 +266,17 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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/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/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/large_objects.dm b/code/modules/cargo/exports/large_objects.dm index 2943130a19..9202fd1f81 100644 --- a/code/modules/cargo/exports/large_objects.dm +++ b/code/modules/cargo/exports/large_objects.dm @@ -171,11 +171,11 @@ var/worth = 10 worth += C.air_contents.get_moles(/datum/gas/bz)*3 worth += C.air_contents.get_moles(/datum/gas/stimulum)*25 - worth += C.air_contents.get_moles(/datum/gas/hypernoblium)*1000 + worth += C.air_contents.get_moles(/datum/gas/hypernoblium)*20 worth += C.air_contents.get_moles(/datum/gas/miasma)*2 worth += C.air_contents.get_moles(/datum/gas/tritium)*7 worth += C.air_contents.get_moles(/datum/gas/pluoxium)*6 - worth += C.air_contents.get_moles(/datum/gas/nitryl)*30 + worth += C.air_contents.get_moles(/datum/gas/nitryl)*10 return worth 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/client/client_procs.dm b/code/modules/client/client_procs.dm index 970e34cabe..8add693d48 100644 --- a/code/modules/client/client_procs.dm +++ b/code/modules/client/client_procs.dm @@ -419,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) @@ -427,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() @@ -523,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 @@ -538,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 @@ -559,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.") @@ -609,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 @@ -622,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 @@ -635,14 +655,20 @@ 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) @@ -666,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 @@ -684,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 @@ -702,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 @@ -722,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) @@ -736,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() @@ -747,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") @@ -778,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 @@ -790,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 @@ -970,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() @@ -1011,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()) @@ -1050,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 dbbf648d1f..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() @@ -162,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" @@ -211,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)] @@ -1355,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 @@ -1372,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 @@ -1609,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 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/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/under/suits.dm b/code/modules/clothing/under/suits.dm index b9f55e695a..7f0ecf3d70 100644 --- a/code/modules/clothing/under/suits.dm +++ b/code/modules/clothing/under/suits.dm @@ -138,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/abductor.dm b/code/modules/events/abductor.dm index 25a5cb2ffa..41aab20a11 100755 --- a/code/modules/events/abductor.dm +++ b/code/modules/events/abductor.dm @@ -3,7 +3,7 @@ typepath = /datum/round_event/ghost_role/abductor weight = 10 max_occurrences = 1 - min_players = 20 + min_players = 30 gamemode_blacklist = list("nuclear","wizard","revolution","dynamic") /datum/round_event/ghost_role/abductor 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/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 90ebe7f6a1..a9ee0f5412 100644 --- a/code/modules/events/immovable_rod.dm +++ b/code/modules/events/immovable_rod.dm @@ -160,6 +160,7 @@ In my current plan for it, 'solid' will be defined as anything with density == 1 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()) 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..a109d484e9 --- /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(20)) + 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.001 * power + var/explosion_size = rand(1000000000, 999999999) + var/turf/epicenter = get_turf_in_angle(supernova.azimuth, SSmapping.get_station_center(), round(world.maxx * 0.45)) + 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 = round((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 < 4 && !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_upper = 100 + telegraph_duration = 100 + radiation_intensity = 50 diff --git a/code/modules/food_and_drinks/drinks/drinks.dm b/code/modules/food_and_drinks/drinks/drinks.dm index 807251111a..6b8de787f0 100644 --- a/code/modules/food_and_drinks/drinks/drinks.dm +++ b/code/modules/food_and_drinks/drinks/drinks.dm @@ -492,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) 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 b79efdd39b..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) @@ -282,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() @@ -365,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) @@ -377,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/)) @@ -389,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)) @@ -407,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) @@ -431,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) . = ..() @@ -446,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) @@ -463,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( @@ -504,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( @@ -525,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/hydroponics/hydroponics.dm b/code/modules/hydroponics/hydroponics.dm index e186364cff..aca727ad8d 100644 --- a/code/modules/hydroponics/hydroponics.dm +++ b/code/modules/hydroponics/hydroponics.dm @@ -97,6 +97,7 @@ if(myseed.mutatelist.len > 0) myseed.instability = (myseed.instability/2) mutatespecie() + return BULLET_ACT_HIT else return ..() 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/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/library/lib_items.dm b/code/modules/library/lib_items.dm index ef0ee2ee77..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(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(I.tool_behaviour == TOOL_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(I.tool_behaviour == TOOL_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() @@ -107,19 +127,22 @@ 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,32 +280,32 @@ 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) || I.tool_behaviour == TOOL_WIRECUTTER) to_chat(user, "You begin to carve out [title]...") @@ -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/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/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/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/machine_silo.dm b/code/modules/mining/machine_silo.dm index b0055204f9..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 diff --git a/code/modules/mining/machine_stacking.dm b/code/modules/mining/machine_stacking.dm index d27e49b48c..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,49 +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(I.tool_behaviour == TOOL_MULTITOOL) - I.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**************************/ @@ -107,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 @@ -114,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/mob/dead/new_player/new_player.dm b/code/modules/mob/dead/new_player/new_player.dm index dbab6a558c..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,71 +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 - //parameters used by sql line, easier to read: - var/bantype_str = "ADMIN_PERMABAN" - var/reason = "SYSTEM BAN - Inputted date during join verification was under 18 years of age. Contact administration on discord for verification." - var/duration = -1 - var/sql_ckey = sanitizeSQL(client.ckey) - var/computerid = client.computer_id - if(!computerid) - computerid = "0" - var/sql_computerid = sanitizeSQL(computerid) - var/ip = client.address - if(!ip) - ip = "0.0.0.0" - var/sql_ip = sanitizeSQL(ip) + if(!(client.prefs.db_flags & DB_FLAG_AGE_CONFIRMATION_INCOMPLETE)) //completed? Skip + return TRUE - //parameter not used as there's no job but i want to fill out all parameters for the insert line - var/sql_job + 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(", ") - // these are typically the banning admin's, but it's the system so we leave them null, but they're still here for the sake of a full set of values - var/sql_a_ckey - var/sql_a_computerid - var/sql_a_ip + 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 + )) - // record all admins and non-admins online at the time - var/who - for(var/client/C in GLOB.clients) - if(!who) - who = "[C]" - else - who += ", [C]" + 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) - var/adminwho - for(var/client/C in GLOB.admins) - if(!adminwho) - adminwho = "[C]" - else - adminwho += ", [C]" + 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") - var/sql = "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(IF('[world.internet_address]' LIKE '', '0', '[world.internet_address]')), '[world.port]', '[GLOB.round_id]', '[bantype_str]', '[reason]', '[sql_job]', [(duration)?"[duration]":"0"], Now() + INTERVAL [(duration>0) ? duration : 0] MINUTE, '[sql_ckey]', '[sql_computerid]', INET_ATON('[sql_ip]'), '[sql_a_ckey]', '[sql_a_computerid]', INET_ATON('[sql_a_ip]'), '[who]', '[adminwho]')" - var/datum/DBQuery/query_add_ban = SSdbcore.NewQuery(sql) - qdel(query_add_ban) + // 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.") - // announce this - message_admins("[html_encode(client.ckey)] has been banned for failing the automatic age gate.") - send2irc("[html_encode(client.ckey)] has been banned for failing the automatic age gate.") + // removing the client disconnects them + qdel(client) - // 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 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 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/hair_head.dm b/code/modules/mob/dead/new_player/sprite_accessories/hair_head.dm index 0c22a85886..a4bd0afdcb 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 @@ -477,6 +477,10 @@ name = "Messy" icon_state = "hair_messy" +/datum/sprite_accessory/hair/messy2 + name = "Messy2" + icon_state = "hair_messy2" + /datum/sprite_accessory/hair/modern name = "Modern" icon_state = "hair_modern" 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/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/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm index 10abc87237..d0af9782bd 100644 --- a/code/modules/mob/living/carbon/human/human_defines.dm +++ b/code/modules/mob/living/carbon/human/human_defines.dm @@ -75,6 +75,7 @@ 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 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 07f35f9ce0..0e6ad74e99 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) 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/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/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/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/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 e5ba022277..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 diff --git a/code/modules/mob/living/life.dm b/code/modules/mob/living/life.dm index a6fdfdc793..0ce5af12b9 100644 --- a/code/modules/mob/living/life.dm +++ b/code/modules/mob/living/life.dm @@ -3,7 +3,7 @@ * Splits off into PhysicalLife() and BiologicalLife(). Override those instead of this. */ /mob/living/proc/Life(seconds, times_fired) - //SHOULD_NOT_SLEEP(TRUE) + SHOULD_NOT_SLEEP(TRUE) if(mob_transforming) return @@ -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) + INVOKE_ASYNC(GLOBAL_PROC, .proc/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/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm index e0e1d91ba9..ce6a5dcda1 100644 --- a/code/modules/mob/living/silicon/ai/ai.dm +++ b/code/modules/mob/living/silicon/ai/ai.dm @@ -200,7 +200,7 @@ if(option == "Random") iconstates[option] = image(icon = src.icon, icon_state = "ai-random") continue - iconstates[option] = image(icon = src.icon, icon_state = resolve_ai_icon(option)) + iconstates[option] = image(icon = src.icon, icon_state = resolve_ai_icon(option, radial_preview = TRUE)) view_core() var/ai_core_icon = show_radial_menu(src, src , iconstates, radius = 42) 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/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 36b5edcbc9..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) @@ -475,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 ..() @@ -520,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() @@ -599,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 @@ -609,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" @@ -626,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) @@ -661,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 @@ -694,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() . = ..() @@ -735,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) @@ -797,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) @@ -834,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) @@ -849,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 @@ -862,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) ..() @@ -901,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) @@ -910,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. @@ -935,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 @@ -949,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) @@ -959,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)) @@ -972,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 @@ -981,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 @@ -1069,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 @@ -1080,6 +1193,7 @@ /mob/living/silicon/robot/shell shell = TRUE + cell = null /mob/living/silicon/robot/MouseDrop_T(mob/living/M, mob/living/user) . = ..() @@ -1090,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!") @@ -1120,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) @@ -1138,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" @@ -1178,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 4c83841809..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() diff --git a/code/modules/mob/living/silicon/robot/update_icons.dm b/code/modules/mob/living/silicon/robot/update_icons.dm index a567446e9e..088c93cff1 100644 --- a/code/modules/mob/living/silicon/robot/update_icons.dm +++ b/code/modules/mob/living/silicon/robot/update_icons.dm @@ -23,13 +23,17 @@ if(module.cyborg_base_icon == "robot") icon = 'icons/mob/robots.dmi' pixel_x = initial(pixel_x) - if(stat != DEAD && !(IsUnconscious() ||IsStun() || IsKnockdown() || IsParalyzed() || low_power_mode)) //Not dead, not stunned. + if(stat != DEAD && !(IsUnconscious() || IsStun() || IsParalyzed() || low_power_mode)) //Not dead, not stunned. if(!eye_lights) eye_lights = new() - if(lamp_intensity > 2) + if(lamp_enabled || lamp_doom) eye_lights.icon_state = "[module.special_light_key ? "[module.special_light_key]":"[module.cyborg_base_icon]"]_l" + eye_lights.color = lamp_doom? COLOR_RED : lamp_color + eye_lights.plane = 19 //glowy eyes else eye_lights.icon_state = "[module.special_light_key ? "[module.special_light_key]":"[module.cyborg_base_icon]"]_e[is_servant_of_ratvar(src) ? "_r" : ""]" + eye_lights.color = COLOR_WHITE + eye_lights.plane = -1 eye_lights.icon = icon add_overlay(eye_lights) 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/friendly/dog.dm b/code/modules/mob/living/simple_animal/friendly/dog.dm index 49581b91a7..e3486a40e2 100644 --- a/code/modules/mob/living/simple_animal/friendly/dog.dm +++ b/code/modules/mob/living/simple_animal/friendly/dog.dm @@ -425,7 +425,9 @@ /mob/living/simple_animal/pet/dog/corgi/Ian/BiologicalLife() if(!(. = ..())) return + INVOKE_ASYNC(src, .proc/corgi_ai_behavior) +/mob/living/simple_animal/pet/dog/corgi/Ian/proc/corgi_ai_behavior() //Feeding, chasing food, FOOOOODDDD if(!stat && CHECK_MULTIPLE_BITFIELDS(mobility_flags, MOBILITY_STAND|MOBILITY_MOVE) && !buckled) turns_since_scan++ 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/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/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/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 a584d34995..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) 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/hivelord.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/hivelord.dm index ae0f5ea3ca..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 diff --git a/code/modules/mob/living/simple_animal/slime/slime.dm b/code/modules/mob/living/simple_animal/slime/slime.dm index 06649ac7d6..f6169be902 100644 --- a/code/modules/mob/living/simple_animal/slime/slime.dm +++ b/code/modules/mob/living/simple_animal/slime/slime.dm @@ -252,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/transform_procs.dm b/code/modules/mob/transform_procs.dm index e49bd5162b..fcaa77cac4 100644 --- a/code/modules/mob/transform_procs.dm +++ b/code/modules/mob/transform_procs.dm @@ -178,10 +178,7 @@ 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() 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 e53f4087ab..131a098258 100644 --- a/code/modules/modular_computers/computers/item/computer.dm +++ b/code/modules/modular_computers/computers/item/computer.dm @@ -21,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) @@ -57,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) @@ -104,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() @@ -113,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) @@ -199,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] @@ -211,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. @@ -240,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 @@ -248,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() @@ -345,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] @@ -387,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 002cf20801..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) + 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) + 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) + 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) + 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) + 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) + 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) + 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) + 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 3455547d20..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]
    @@ -175,7 +177,7 @@ if("PRG_edit") if(!computer || !authenticated || !target_id_card) return - var/new_name = reject_bad_name(params["name"]) + var/new_name = params["name"] if(!new_name) return target_id_card.registered_name = new_name @@ -190,7 +192,7 @@ return if(target == "Custom") - var/custom_name = reject_bad_name(params["custom_name"]) + var/custom_name = params["custom_name"] if(custom_name) target_id_card.assignment = custom_name target_id_card.update_label() @@ -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 46d10afe7f..5c4bf2ed64 100644 --- a/code/modules/movespeed/_movespeed_modifier.dm +++ b/code/modules/movespeed/_movespeed_modifier.dm @@ -161,8 +161,10 @@ GLOBAL_LIST_EMPTY(movespeed_modification_cache) /// Handles the special case of editing the movement var /mob/vv_edit_var(var_name, var_value) if(var_name == NAMEOF(src, control_object)) - var/obj/O = var_name - if(!istype(O) || (O.obj_flags & DANGEROUS_POSSESSION)) + var/obj/O = var_value + if(!istype(O) && (var_value != null)) + return FALSE + if(O.obj_flags & DANGEROUS_POSSESSION) return FALSE var/slowdown_edit = (var_name == NAMEOF(src, cached_multiplicative_slowdown)) var/diff 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 a00145f9dc..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() @@ -193,13 +189,6 @@ user.visible_message(ignition_message) add_fingerprint(user) fire_act(I.get_temperature()) -//I would have it become a paper plane before the throw, but that would risk runtimes -/obj/item/paper/DoRevenantThrowEffects(atom/target) - sleep(10) - if(HAS_TRAIT(src, TRAIT_SPOOKY_THROW)) - return - new /obj/item/paperplane(get_turf(src)) - qdel(src) /obj/item/paper/attackby(obj/item/P, mob/living/user, params) if(burn_paper_product_attackby_check(P, user)) @@ -253,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 @@ -291,7 +282,8 @@ return data /obj/item/paper/ui_act(action, params,datum/tgui/ui) - if(..()) + . = ..() + if(.) return switch(action) if("stamp") @@ -317,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 9f4a864e9c..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 @@ -22,11 +23,10 @@ var/list/obj/item/ticket_machine_ticket/tickets = list() /obj/machinery/ticket_machine/multitool_act(mob/living/user, obj/item/I) - if(!I.tool_behaviour == TOOL_MULTITOOL) - return if(!multitool_check_buffer(user, I)) //make sure it has a data buffer return - I.buffer = src + var/obj/item/multitool/M = I + M.buffer = src to_chat(user, "You store linkage information in [I]'s buffer.") return TRUE @@ -78,10 +78,11 @@ /obj/machinery/button/ticket_machine/multitool_act(mob/living/user, obj/item/I) . = ..() if(I.tool_behaviour == TOOL_MULTITOOL) - if(I.buffer && !istype(I.buffer, /obj/machinery/ticket_machine)) + var/obj/item/multitool/M = I + if(M.buffer && !istype(M.buffer, /obj/machinery/ticket_machine)) return var/obj/item/assembly/control/ticket_machine/controller = device - controller.linked = I.buffer + controller.linked = M.buffer id = null controller.id = null to_chat(user, "You've linked [src] to [controller.linked].") @@ -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/power/cable.dm b/code/modules/power/cable.dm index 1058277cdf..9e779b9c1b 100644 --- a/code/modules/power/cable.dm +++ b/code/modules/power/cable.dm @@ -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/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/solar.dm b/code/modules/power/solar.dm index da99ecaf73..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 @@ -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)) @@ -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/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/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/guns/ballistic/revolver.dm b/code/modules/projectiles/guns/ballistic/revolver.dm index 06342937dc..0d60203cb5 100644 --- a/code/modules/projectiles/guns/ballistic/revolver.dm +++ b/code/modules/projectiles/guns/ballistic/revolver.dm @@ -339,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) @@ -390,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/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/projectile.dm b/code/modules/projectiles/projectile.dm index 481d3c2c68..0a7598cd79 100644 --- a/code/modules/projectiles/projectile.dm +++ b/code/modules/projectiles/projectile.dm @@ -516,7 +516,7 @@ trajectory = new(starting.x, starting.y, starting.z, pixel_x, pixel_y, Angle, pixel_increment_amount) fired = TRUE if(hitscan) - process_hitscan() + INVOKE_ASYNC(src, .proc/process_hitscan) return if(!(datum_flags & DF_ISPROCESSING)) START_PROCESSING(SSprojectiles, src) diff --git a/code/modules/reagents/chemistry/recipes/drugs.dm b/code/modules/reagents/chemistry/recipes/drugs.dm index 8de4b1248f..cd55d00cf1 100644 --- a/code/modules/reagents/chemistry/recipes/drugs.dm +++ b/code/modules/reagents/chemistry/recipes/drugs.dm @@ -88,9 +88,9 @@ mix_message = "The mixture boils off a yellow, smelly vapor..."//Sulfur burns off, leaving the camphor /datum/chemical_reaction/anaphroplus - name = "pentacamphor" + name = "hexacamphor" id = /datum/reagent/drug/anaphrodisiacplus results = list(/datum/reagent/drug/anaphrodisiacplus = 1) - required_reagents = list(/datum/reagent/drug/aphrodisiac = 5, /datum/reagent/acetone = 1) - required_temp = 300 + 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/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/medical_designs.dm b/code/modules/research/designs/medical_designs.dm index 9879171e71..471b2f931e 100644 --- a/code/modules/research/designs/medical_designs.dm +++ b/code/modules/research/designs/medical_designs.dm @@ -809,22 +809,22 @@ research_icon_state = "surgery_chest" /datum/design/surgery/healing/brute_upgrade - name = "Tend Wounds (Brute) Upgrade" + name = "Tend Wounds (Brute) Upgrade I" surgery = /datum/surgery/healing/brute/upgraded id = "surgery_heal_brute_upgrade" /datum/design/surgery/healing/brute_upgrade_2 - name = "Tend Wounds (Brute) Upgrade" + name = "Tend Wounds (Brute) Upgrade II" surgery = /datum/surgery/healing/brute/upgraded/femto id = "surgery_heal_brute_upgrade_femto" /datum/design/surgery/healing/burn_upgrade - name = "Tend Wounds (Burn) Upgrade" + name = "Tend Wounds (Burn) Upgrade I" surgery = /datum/surgery/healing/burn/upgraded id = "surgery_heal_burn_upgrade" /datum/design/surgery/healing/burn_upgrade_2 - name = "Tend Wounds (Burn) Upgrade" + name = "Tend Wounds (Burn) Upgrade II" surgery = /datum/surgery/healing/brute/upgraded/femto id = "surgery_heal_burn_upgrade_femto" @@ -835,16 +835,26 @@ id = "surgery_heal_combo" /datum/design/surgery/healing/combo_upgrade - name = "Tend Wounds (Mixture) Upgrade" + name = "Tend Wounds (Mixture) Upgrade I" surgery = /datum/surgery/healing/combo/upgraded id = "surgery_heal_combo_upgrade" /datum/design/surgery/healing/combo_upgrade_2 - name = "Tend Wounds (Mixture) Upgrade" + name = "Tend Wounds (Mixture) Upgrade II" desc = "A surgical procedure that repairs both bruises and burns faster than their individual counterparts. It is more effective than both the individual surgeries." surgery = /datum/surgery/healing/combo/upgraded/femto id = "surgery_heal_combo_upgrade_femto" +/datum/design/surgery/healing/robot_upgrade + name = "Repair Robotic Limbs Upgrade" + surgery = /datum/surgery/robot_healing/upgraded + id = "surgery_heal_robo_upgrade" + +/datum/design/surgery/healing/robot_upgrade_2 + name = "Repair Robotic Limbs Upgrade II" + surgery = /datum/surgery/robot_healing/upgraded/femto + id = "surgery_heal_robo_upgrade_femto" + /datum/design/surgery/surgery_toxinhealing name = "Body Rejuvenation" desc = "A surgical procedure that helps deal with oxygen deprecation, and treat toxic damaged. Works on corpses and alive alike without chemicals." 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/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/techweb/nodes/medical_nodes.dm b/code/modules/research/techweb/nodes/medical_nodes.dm index 914a6174ab..af23804c57 100644 --- a/code/modules/research/techweb/nodes/medical_nodes.dm +++ b/code/modules/research/techweb/nodes/medical_nodes.dm @@ -116,6 +116,14 @@ design_ids = list("surgery_heal_brute_upgrade","surgery_heal_burn_upgrade") research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 1000) +/datum/techweb_node/adv_surgery + id = "adv_surgery" + display_name = "Advanced Surgery" + description = "When simple medicine doesn't cut it." + prereq_ids = list("imp_wt_surgery") + design_ids = list("surgery_revival", "surgery_lobotomy", "surgery_heal_brute_upgrade_femto","surgery_heal_burn_upgrade_femto","surgery_heal_robo_upgrade","surgery_heal_combo", "surgery_toxinhealing", "organbox", "surgery_adv_dissection") + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500) + /datum/techweb_node/advance_surgerytools id = "advance_surgerytools" display_name = "Advanced Surgery Tools" @@ -124,20 +132,12 @@ design_ids = list("drapes", "retractor_adv", "surgicaldrill_adv", "scalpel_adv", "bonesetter", "surgical_tape") research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500) -/datum/techweb_node/adv_surgery - id = "adv_surgery" - display_name = "Advanced Surgery" - description = "When simple medicine doesn't cut it." - prereq_ids = list("imp_wt_surgery") - design_ids = list("surgery_revival", "surgery_lobotomy", "surgery_heal_brute_upgrade_femto","surgery_heal_burn_upgrade_femto", "surgery_heal_combo", "surgery_toxinhealing", "organbox", "surgery_adv_dissection") - research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500) - /datum/techweb_node/exp_surgery id = "exp_surgery" display_name = "Experimental Surgery" description = "When evolution isn't fast enough." prereq_ids = list("adv_surgery") - design_ids = list("surgery_pacify","surgery_vein_thread","surgery_muscled_veins","surgery_nerve_splice","surgery_nerve_ground","surgery_ligament_hook","surgery_ligament_reinforcement","surgery_viral_bond", "surgery_exp_dissection", "surgery_heal_combo_upgrade") + design_ids = list("surgery_pacify","surgery_vein_thread","surgery_muscled_veins","surgery_nerve_splice","surgery_nerve_ground","surgery_ligament_hook","surgery_ligament_reinforcement","surgery_viral_bond", "surgery_exp_dissection","surgery_heal_robo_upgrade_femto","surgery_heal_combo_upgrade") research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 5000) /datum/techweb_node/alien_surgery diff --git a/code/modules/ruins/objects_and_mobs/necropolis_gate.dm b/code/modules/ruins/objects_and_mobs/necropolis_gate.dm index 5f9b810070..d62b3c2611 100644 --- a/code/modules/ruins/objects_and_mobs/necropolis_gate.dm +++ b/code/modules/ruins/objects_and_mobs/necropolis_gate.dm @@ -12,6 +12,7 @@ pixel_x = -32 pixel_y = -32 resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + rad_flags = RAD_NO_CONTAMINATE | RAD_PROTECT_CONTENTS light_range = 8 light_color = LIGHT_COLOR_LAVA var/open = FALSE @@ -71,6 +72,7 @@ /obj/structure/opacity_blocker icon = 'icons/effects/96x96.dmi' icon_state = "gate_blocker" + rad_flags = RAD_NO_CONTAMINATE | RAD_PROTECT_CONTENTS layer = EDGED_TURF_LAYER pixel_x = -32 pixel_y = -32 @@ -252,6 +254,7 @@ GLOBAL_DATUM(necropolis_gate, /obj/structure/necropolis_gate/legion_gate) icon = 'icons/turf/boss_floors.dmi' icon_state = "pristine_tile1" layer = ABOVE_OPEN_TURF_LAYER + rad_flags = RAD_NO_CONTAMINATE | RAD_PROTECT_CONTENTS anchored = TRUE resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF var/tile_key = "pristine_tile" 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 c8ecbf1906..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) @@ -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/smithing/smithed_items.dm b/code/modules/smithing/smithed_items.dm index e514fd5c9a..aff296b684 100644 --- a/code/modules/smithing/smithed_items.dm +++ b/code/modules/smithing/smithed_items.dm @@ -394,7 +394,7 @@ name = "smithed katana blade" finishingitem = /obj/item/swordhandle finalitem = /obj/item/melee/smith/twohand/katana - icon_state = "katana" + icon_state = "katana-s" /obj/item/smithing/katanablade/startfinish() 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/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/surgery/healing.dm b/code/modules/surgery/healing.dm index f5b23e6087..dd655a0ea5 100644 --- a/code/modules/surgery/healing.dm +++ b/code/modules/surgery/healing.dm @@ -169,7 +169,6 @@ /***************************COMBO***************************/ /datum/surgery/healing/combo - /datum/surgery/healing/combo name = "Tend Wounds (Mixture, Basic)" replaced_by = /datum/surgery/healing/combo/upgraded diff --git a/code/modules/surgery/robot_healing.dm b/code/modules/surgery/robot_healing.dm index f473ef280e..6c9ff6e78f 100644 --- a/code/modules/surgery/robot_healing.dm +++ b/code/modules/surgery/robot_healing.dm @@ -1,9 +1,9 @@ //Almost copypaste of tend wounds, with some changes /datum/surgery/robot_healing - name = "Repair robotic limbs (basic)" + name = "Repair Robotic Limbs" desc = "A surgical procedure that provides repairs and maintenance to robotic limbs. Is slightly more efficient when the patient is severely damaged." - + replaced_by = /datum/surgery steps = list(/datum/surgery_step/mechanic_open, /datum/surgery_step/pry_off_plating, /datum/surgery_step/cut_wires, @@ -14,8 +14,28 @@ possible_locs = list(BODY_ZONE_CHEST) requires_bodypart_type = 0 //You can do this on anyone, but it won't really be useful on people without augments. ignore_clothes = TRUE + var/healing_step_type var/antispam = FALSE - var/healing_step_type = /datum/surgery_step/robot_heal/basic + +/datum/surgery/robot_healing/basic + name = "Repair Robotic Limbs (Basic)" + replaced_by = /datum/surgery/robot_healing/upgraded + healing_step_type = /datum/surgery_step/robot_heal/basic + desc = "A surgical procedure that provides basic repairs and maintenance to a patient's robotic limbs. Heals slightly more when the patient is severely injured." + +/datum/surgery/robot_healing/upgraded + name = "Repair Robotic Limbs (Adv.)" + requires_tech = TRUE + replaced_by = /datum/surgery/robot_healing/upgraded/femto + healing_step_type = /datum/surgery_step/robot_heal/upgraded + desc = "A surgical procedure that provides advanced repairs and maintenance to a patient's robotic limbs. Heals more when the patient is severely injured." + +/datum/surgery/robot_healing/upgraded/femto + name = "Repair Robotic Limbs (Exp.)" + requires_tech = TRUE + replaced_by = null // as good as it gets + healing_step_type = /datum/surgery_step/robot_heal/upgraded/femto + desc = "A surgical procedure that provides experimental repairs and maintenance to a patient's robotic limbs. Heals considerably more when the patient is severely injured." /datum/surgery/robot_healing/New(surgery_target, surgery_location, surgery_bodypart) ..() @@ -35,26 +55,21 @@ var/healsburn = FALSE var/brutehealing = 0 var/burnhealing = 0 - var/missinghpbonus = 0 //heals an extra point of damager per X missing damage of type (burn damage for burn healing, brute for brute). Smaller Number = More Healing! + var/missinghpbonus = 0 //heals an extra point of damage per X missing damage of type (burn damage for burn healing, brute for brute). Smaller Number = More Healing! /datum/surgery_step/robot_heal/tool_check(mob/user, obj/item/tool) if(implement_type == TOOL_WELDER && !tool.tool_use_check(user, 1)) return FALSE return TRUE -/datum/surgery/robot_healing/can_start(mob/user, mob/living/carbon/target, obj/item/tool) - var/possible = FALSE +/datum/surgery/robot_healing/can_start(mob/user, mob/living/carbon/target, obj/item/tool) // hey delta? why is the check for this all the way down here for(var/obj/item/bodypart/B in target.bodyparts) if(B.is_robotic_limb()) - possible = TRUE - break - if(!possible) - return FALSE - return TRUE + return ..() /datum/surgery_step/robot_heal/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) var/woundtype - if(implement_type == TOOL_WELDER) + if(implement_type == TOOL_WELDER) healsbrute = TRUE healsburn = FALSE woundtype = "dents" @@ -133,3 +148,13 @@ brutehealing = 10 burnhealing = 10 missinghpbonus = 15 + +/datum/surgery_step/robot_heal/upgraded + brutehealing = 10 + burnhealing = 10 + missinghpbonus = 10 + +/datum/surgery_step/robot_heal/upgraded/femto + brutehealing = 10 + burnhealing = 10 + missinghpbonus = 5 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 index 54d6d5abd9..f5c7c47aaf 100644 --- a/code/modules/tcg/cards.dm +++ b/code/modules/tcg/cards.dm @@ -381,6 +381,14 @@ . = ..() 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) @@ -797,7 +805,13 @@ 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 - card_types[card.datum_type] = card.illegal + 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 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/uplink/uplink_items/uplink_clothing.dm b/code/modules/uplink/uplink_items/uplink_clothing.dm index de15b16b68..5471eb9f31 100644 --- a/code/modules/uplink/uplink_items/uplink_clothing.dm +++ b/code/modules/uplink/uplink_items/uplink_clothing.dm @@ -98,6 +98,7 @@ item = /obj/item/clothing/gloves/tackler/combat/insulated include_modes = list(/datum/game_mode/nuclear, /datum/game_mode/nuclear/clown_ops) cost = 2 + illegal_tech = FALSE /datum/uplink_item/device_tools/syndicate_eyepatch name = "Mechanical Eyepatch" diff --git a/code/modules/uplink/uplink_items/uplink_devices.dm b/code/modules/uplink/uplink_items/uplink_devices.dm index 5f5eb91a04..ad1cc31ba7 100644 --- a/code/modules/uplink/uplink_items/uplink_devices.dm +++ b/code/modules/uplink/uplink_items/uplink_devices.dm @@ -130,6 +130,7 @@ multitool and combat gloves that are resistant to shocks and heat." item = /obj/item/storage/toolbox/syndicate cost = 1 + illegal_tech = FALSE /datum/uplink_item/device_tools/syndie_glue name = "Glue" diff --git a/code/modules/uplink/uplink_items/uplink_stealthdevices.dm b/code/modules/uplink/uplink_items/uplink_stealthdevices.dm index 28d02cf79b..0a82b3b8b3 100644 --- a/code/modules/uplink/uplink_items/uplink_stealthdevices.dm +++ b/code/modules/uplink/uplink_items/uplink_stealthdevices.dm @@ -16,6 +16,7 @@ with these cards." item = /obj/item/card/id/syndicate cost = 2 + illegal_tech = FALSE /datum/uplink_item/stealthy_tools/ai_detector name = "Artificial Intelligence Detector" 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/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 7776d87d4e..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. 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 e8709d10b1..0fbad2153c 100644 --- a/dependencies.sh +++ b/dependencies.sh @@ -1,23 +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.7 #node version export NODE_VERSION=12 +export NODE_VERSION_PRECISE=12.20.0 # SpacemanDMM git tag export SPACEMAN_DMM_VERSION=suite-1.6 # Extools git tag -export EXTOOLS_VERSION=v0.0.6 +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 65e2d571a2..a91f16253c 100644 --- a/html/changelog.html +++ b/html/changelog.html @@ -50,6 +50,147 @@ -->
    +

    14 March 2021

    +

    Adelphon updated:

    +
      +
    • messy2
    • +
    • papermask
    • +
    +

    Hatterhat updated:

    +
      +
    • Robotic limb repair surgery now has tiers.
    • +
    • Pubby and Delta now have roundstart limb-growers relatively close to, if not in, the operating theaters. tweak: Box QM's console now can announce things.
    • +
    +

    Putnam3145 updated:

    +
      +
    • chaos loads now
    • +
    +

    kiwedespars updated:

    +
      +
    • adds a cute new plushie
    • +
    +

    necromanceranne updated:

    +
      +
    • Fixes cosmetic augments missing their foot sprites.
    • +
    + +

    12 March 2021

    +

    R3dtail updated:

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

    10 March 2021

    +

    Hatterhat updated:

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

    Putnam3145 updated:

    +
      +
    • uncapped TEG power, buffing high-temp TEGs
    • +
    + +

    09 March 2021

    +

    LetterN updated:

    +
      +
    • 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:

    +
      +
    • exiting a bluespace jar through any means, hardstuns you for 5 seconds
    • +
    + +

    05 March 2021

    +

    Putnam3145 updated:

    +
      +
    • Lowered ash storm volume
    • +
    • Minesweeper can no longer be made to lag the server on purpose
    • +
    +

    keronshb updated:

    +
      +
    • Prevents heat from going through reinforced plasma glass.
    • +
    + +

    04 March 2021

    +

    LetterN updated:

    +
      +
    • removes bsql
    • +
    + +

    03 March 2021

    +

    MarinaGryphon updated:

    +
      +
    • The AOOC mute pref is now properly respected.
    • +
    • Muting adminhelp sounds no longer mutes AOOC.
    • +
    +

    Putnam3145 updated:

    +
      +
    • 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:

    +
      +
    • Legions should now pass their type to the person they infect (if valid).
    • +
    +

    dzahlus updated:

    +
      +
    • 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
    • +
    +

    qweq12yt updated:

    +
      +
    • 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
    • +
    + +

    02 March 2021

    +

    LetterN updated:

    +
      +
    • colorpainter: let's not dispense null
    • +
    +

    SandPoot updated:

    +
      +
    • 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:

    +
      +
    • Fixes decks breaking your screen
    • +
    • Fixes binders not saving cards
    • +
    • Fixes binders not saving multiple cards of the same type
    • +
    +

    Vynzill updated:

    +
      +
    • cursed rice hat right in front of the jungle gateway's entrance is now removed from this dimensional plane
    • +
    +

    28 February 2021

    Putnam3145 updated:

      diff --git a/html/changelogs/.all_changelog.yml b/html/changelogs/.all_changelog.yml index 6ba2436d94..a40c2bec77 100644 --- a/html/changelogs/.all_changelog.yml +++ b/html/changelogs/.all_changelog.yml @@ -28628,3 +28628,94 @@ DO NOT EDIT THIS FILE BY HAND! AUTOMATICALLY GENERATED BY ss13_genchangelog.py. - 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-12: + R3dtail: + - spellcheck: Adds Periods and moves some words around. +2021-03-14: + Adelphon: + - rscadd: messy2 + - bugfix: papermask + Hatterhat: + - rscadd: Robotic limb repair surgery now has tiers. + - rscadd: 'Pubby and Delta now have roundstart limb-growers relatively close to, + if not in, the operating theaters. tweak: Box QM''s console now can announce + things.' + Putnam3145: + - bugfix: chaos loads now + kiwedespars: + - rscadd: adds a cute new plushie + necromanceranne: + - bugfix: Fixes cosmetic augments missing their foot sprites. diff --git a/html/changelogs/AutoChangeLog-pr-14397.yml b/html/changelogs/AutoChangeLog-pr-14397.yml new file mode 100644 index 0000000000..ba93472587 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-14397.yml @@ -0,0 +1,4 @@ +author: "SandPoot" +delete-after: True +changes: + - bugfix: "Fixed interacting with telecomms." diff --git a/html/changelogs/AutoChangeLog-pr-14398.yml b/html/changelogs/AutoChangeLog-pr-14398.yml new file mode 100644 index 0000000000..ff5999c8d4 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-14398.yml @@ -0,0 +1,7 @@ +author: "LetterN" +delete-after: True +changes: + - bugfix: "Borg light icons not turning off" + - bugfix: "Double ai icon select + Fixes ai core not having icons" + - bugfix: "Missing air tank icon" + - bugfix: "Computer boards being dumb and nullspacing/qdeling itself" diff --git a/html/changelogs/AutoChangeLog-pr-14421.yml b/html/changelogs/AutoChangeLog-pr-14421.yml new file mode 100644 index 0000000000..f86bb440bf --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-14421.yml @@ -0,0 +1,4 @@ +author: "GrayRachnid" +delete-after: True +changes: + - balance: "removed red toolbox, agent id, and guerilla gloves from unlocking illegal tech." diff --git a/html/changelogs/AutoChangeLog-pr-14433.yml b/html/changelogs/AutoChangeLog-pr-14433.yml new file mode 100644 index 0000000000..a74cf85bc9 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-14433.yml @@ -0,0 +1,4 @@ +author: "HeroWithYay" +delete-after: True +changes: + - bugfix: "fixed spelling error" diff --git a/html/changelogs/AutoChangeLog-pr-14437.yml b/html/changelogs/AutoChangeLog-pr-14437.yml new file mode 100644 index 0000000000..1e2dbadbfe --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-14437.yml @@ -0,0 +1,4 @@ +author: "Putnam3145" +delete-after: True +changes: + - balance: "Supernova now much lower chance to be inconsequential" diff --git a/html/changelogs/AutoChangeLog-pr-14439.yml b/html/changelogs/AutoChangeLog-pr-14439.yml new file mode 100644 index 0000000000..d6bdc95adc --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-14439.yml @@ -0,0 +1,5 @@ +author: "Putnam3145" +delete-after: True +changes: + - balance: "Made hyper-nob's point values way lower (25/20 for science/cargo instead of 1000/1000)" + - balance: "Made nitryl's cargo sell value less (10 instead of 30)" 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("